1 #include "./argumentparser.h"
2 #include "./argumentparserprivate.h"
3 #include "./commandlineutils.h"
4 
5 #include "../conversion/stringbuilder.h"
6 #include "../conversion/stringconversion.h"
7 #include "../io/ansiescapecodes.h"
8 #include "../io/path.h"
9 #include "../misc/levenshtein.h"
10 #include "../misc/parseerror.h"
11 
12 #include <algorithm>
13 #include <cstdlib>
14 #include <cstring>
15 #include <iostream>
16 #include <set>
17 #include <sstream>
18 #include <string>
19 
20 #ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
21 #include <filesystem>
22 #endif
23 
24 using namespace std;
25 using namespace std::placeholders;
26 using namespace std::literals;
27 using namespace CppUtilities::EscapeCodes;
28 
29 /*!
30  *  \namespace CppUtilities
31  *  \brief Contains all utilities provides by the c++utilities library.
32  */
33 namespace CppUtilities {
34 
35 /*!
36  * \brief Returns whether the specified env variable is set to a non-zero and non-white-space-only value.
37  */
isEnvVariableSet(const char * variableName)38 std::optional<bool> isEnvVariableSet(const char *variableName)
39 {
40     const char *envValue = std::getenv(variableName);
41     if (!envValue) {
42         return std::nullopt;
43     }
44     for (; *envValue; ++envValue) {
45         switch (*envValue) {
46         case '0':
47         case ' ':
48             break;
49         default:
50             return true;
51         }
52     }
53     return false;
54 }
55 
56 /*!
57  * \brief The ArgumentDenotationType enum specifies the type of a given argument denotation.
58  */
59 enum ArgumentDenotationType : unsigned char {
60     Value = 0, /**< parameter value */
61     Abbreviation = 1, /**< argument abbreviation */
62     FullName = 2 /**< full argument name */
63 };
64 
65 /*!
66  * \brief The ArgumentCompletionInfo struct holds information internally used for shell completion and suggestions.
67  */
68 struct ArgumentCompletionInfo {
69     ArgumentCompletionInfo(const ArgumentReader &reader);
70 
71     const Argument *const lastDetectedArg;
72     size_t lastDetectedArgIndex = 0;
73     vector<Argument *> lastDetectedArgPath;
74     list<const Argument *> relevantArgs;
75     list<const Argument *> relevantPreDefinedValues;
76     const char *const *lastSpecifiedArg = nullptr;
77     unsigned int lastSpecifiedArgIndex = 0;
78     bool nextArgumentOrValue = false;
79     bool completeFiles = false, completeDirs = false;
80 };
81 
82 /*!
83  * \brief Constructs a new completion info for the specified \a reader.
84  * \remarks Only assigns some defaults. Use ArgumentParser::determineCompletionInfo() to populate the struct with actual data.
85  */
ArgumentCompletionInfo(const ArgumentReader & reader)86 ArgumentCompletionInfo::ArgumentCompletionInfo(const ArgumentReader &reader)
87     : lastDetectedArg(reader.lastArg)
88 {
89 }
90 
91 /// \cond
92 struct ArgumentSuggestion {
93     ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool hasDashPrefix);
94     ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, size_t suggestionSize, bool hasDashPrefix);
95     bool operator<(const ArgumentSuggestion &other) const;
96     bool operator==(const ArgumentSuggestion &other) const;
97     void addTo(multiset<ArgumentSuggestion> &suggestions, size_t limit) const;
98 
99     const char *const suggestion;
100     const size_t suggestionSize;
101     const size_t editingDistance;
102     const bool hasDashPrefix;
103 };
104 
ArgumentSuggestion(const char * unknownArg,size_t unknownArgSize,const char * suggestion,size_t suggestionSize,bool isOperation)105 ArgumentSuggestion::ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, size_t suggestionSize, bool isOperation)
106     : suggestion(suggestion)
107     , suggestionSize(suggestionSize)
108     , editingDistance(computeDamerauLevenshteinDistance(unknownArg, unknownArgSize, suggestion, suggestionSize))
109     , hasDashPrefix(isOperation)
110 {
111 }
112 
ArgumentSuggestion(const char * unknownArg,size_t unknownArgSize,const char * suggestion,bool isOperation)113 ArgumentSuggestion::ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool isOperation)
114     : ArgumentSuggestion(unknownArg, unknownArgSize, suggestion, strlen(suggestion), isOperation)
115 {
116 }
117 
operator <(const ArgumentSuggestion & other) const118 bool ArgumentSuggestion::operator<(const ArgumentSuggestion &other) const
119 {
120     return editingDistance < other.editingDistance;
121 }
122 
addTo(multiset<ArgumentSuggestion> & suggestions,size_t limit) const123 void ArgumentSuggestion::addTo(multiset<ArgumentSuggestion> &suggestions, size_t limit) const
124 {
125     if (suggestions.size() >= limit && !(*this < *--suggestions.end())) {
126         return;
127     }
128     suggestions.emplace(*this);
129     while (suggestions.size() > limit) {
130         suggestions.erase(--suggestions.end());
131     }
132 }
133 /// \endcond
134 
135 /*!
136  * \class ArgumentReader
137  * \brief The ArgumentReader class internally encapsulates the process of reading command line arguments.
138  * \remarks
139  * - For meaning of parameters see documentation of corresponding member variables.
140  * - Results are stored in specified \a args and assigned sub arguments.
141  * - This class is explicitly *not* part of the public API.
142  */
143 
144 /*!
145  * \brief Initializes the internal reader for the specified \a parser and arguments.
146  */
ArgumentReader(ArgumentParser & parser,const char * const * argv,const char * const * end,bool completionMode)147 ArgumentReader::ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode)
148     : parser(parser)
149     , args(parser.m_mainArgs)
150     , index(0)
151     , argv(argv)
152     , end(end)
153     , lastArg(nullptr)
154     , argDenotation(nullptr)
155     , completionMode(completionMode)
156 {
157 }
158 
159 /*!
160  * \brief Resets the ArgumentReader to continue reading new \a argv.
161  */
reset(const char * const * argv,const char * const * end)162 ArgumentReader &ArgumentReader::reset(const char *const *argv, const char *const *end)
163 {
164     this->argv = argv;
165     this->end = end;
166     index = 0;
167     lastArg = nullptr;
168     argDenotation = nullptr;
169     return *this;
170 }
171 
172 /*!
173  * \brief Reads the commands line arguments specified when constructing the object.
174  * \remarks Reads on main-argument-level.
175  */
read()176 bool ArgumentReader::read()
177 {
178     return read(args);
179 }
180 
181 /*!
182  * \brief Returns whether the \a denotation with the specified \a denotationLength matches the argument's \a name.
183  */
matchesDenotation(const char * denotation,size_t denotationLength) const184 bool Argument::matchesDenotation(const char *denotation, size_t denotationLength) const
185 {
186     return m_name && !strncmp(m_name, denotation, denotationLength) && *(m_name + denotationLength) == '\0';
187 }
188 
189 /*!
190  * \brief Reads the commands line arguments specified when constructing the object.
191  * \remarks The argument definitions to look for are specified via \a args. The method calls itself recursively
192  *          to check for nested arguments as well.
193  * \returns Returns true if all arguments have been processed. Returns false on early exit because some argument
194  *          is unknown and behavior for this case is set to UnknownArgumentBehavior::Fail.
195  */
read(ArgumentVector & args)196 bool ArgumentReader::read(ArgumentVector &args)
197 {
198     // method is called recursively for sub args to the last argument (which is nullptr in the initial call) is the current parent argument
199     Argument *const parentArg = lastArg;
200     // determine the current path
201     const vector<Argument *> &parentPath = parentArg ? parentArg->path(parentArg->occurrences() - 1) : vector<Argument *>();
202 
203     Argument *lastArgInLevel = nullptr;
204     vector<const char *> *values = nullptr;
205 
206     // iterate through all argument denotations; loop might exit earlier when an denotation is unknown
207     while (argv != end) {
208         // check whether there are still values to read
209         if (values && lastArgInLevel->requiredValueCount() != Argument::varValueCount && values->size() < lastArgInLevel->requiredValueCount()) {
210             // read arg as value and continue with next arg
211             values->emplace_back(argDenotation ? argDenotation : *argv);
212             ++index;
213             ++argv;
214             argDenotation = nullptr;
215             continue;
216         }
217 
218         // determine how denotation must be processed
219         bool abbreviationFound = false;
220         if (argDenotation) {
221             // continue reading children for abbreviation denotation already detected
222             abbreviationFound = false;
223             argDenotationType = Abbreviation;
224         } else {
225             // determine denotation type
226             argDenotation = *argv;
227             if (!*argDenotation && (!lastArgInLevel || values->size() >= lastArgInLevel->requiredValueCount())) {
228                 // skip empty arguments
229                 ++index;
230                 ++argv;
231                 argDenotation = nullptr;
232                 continue;
233             }
234             abbreviationFound = false;
235             argDenotationType = Value;
236             if (*argDenotation == '-') {
237                 ++argDenotation;
238                 ++argDenotationType;
239                 if (*argDenotation == '-') {
240                     ++argDenotation;
241                     ++argDenotationType;
242                 }
243             }
244         }
245 
246         // try to find matching Argument instance
247         Argument *matchingArg = nullptr;
248         if (argDenotationType != Value) {
249             // determine actual denotation length (everything before equation sign)
250             const char *const equationPos = strchr(argDenotation, '=');
251             const auto argDenotationLength = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation);
252 
253             // loop through each "part" of the denotation
254             // names are read at once, but for abbreviations each character is considered individually
255             for (; argDenotationLength; matchingArg = nullptr) {
256                 // search for arguments by abbreviation or name depending on the previously determined denotation type
257                 if (argDenotationType == Abbreviation) {
258                     for (Argument *const arg : args) {
259                         if (arg->abbreviation() && arg->abbreviation() == *argDenotation) {
260                             matchingArg = arg;
261                             abbreviationFound = true;
262                             break;
263                         }
264                     }
265                 } else {
266                     for (Argument *const arg : args) {
267                         if (arg->matchesDenotation(argDenotation, argDenotationLength)) {
268                             matchingArg = arg;
269                             break;
270                         }
271                     }
272                 }
273                 if (!matchingArg) {
274                     break;
275                 }
276 
277                 // an argument matched the specified denotation so add an occurrence
278                 matchingArg->m_occurrences.emplace_back(index, parentPath, parentArg);
279 
280                 // prepare reading parameter values
281                 values = &matchingArg->m_occurrences.back().values;
282 
283                 // read value after equation sign
284                 if ((argDenotationType != Abbreviation && equationPos) || (++argDenotation == equationPos)) {
285                     values->push_back(equationPos + 1);
286                     argDenotation = nullptr;
287                 }
288 
289                 // read sub arguments, distinguish whether further abbreviations follow
290                 ++index;
291                 ++parser.m_actualArgc;
292                 lastArg = lastArgInLevel = matchingArg;
293                 lastArgDenotation = argv;
294                 if (argDenotationType != Abbreviation || !argDenotation || !*argDenotation) {
295                     // no further abbreviations follow -> read sub args for next argv
296                     ++argv;
297                     argDenotation = nullptr;
298                     read(lastArg->m_subArgs);
299                     argDenotation = nullptr;
300                     break;
301                 } else {
302                     // further abbreviations follow -> remember current arg value
303                     const char *const *const currentArgValue = argv;
304                     // don't increment argv, keep processing outstanding chars of argDenotation
305                     read(lastArg->m_subArgs);
306                     // stop further processing if the denotation has been consumed or even the next value has already been loaded
307                     if (!argDenotation || currentArgValue != argv) {
308                         argDenotation = nullptr;
309                         break;
310                     }
311                 }
312             }
313 
314             // continue with next arg if we've got a match already
315             if (matchingArg) {
316                 continue;
317             }
318 
319             // unknown argument might be a sibling of the parent element
320             for (auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend();; ++parentArgument) {
321                 for (Argument *const sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() : parser.m_mainArgs)) {
322                     if (sibling->occurrences() < sibling->maxOccurrences()) {
323                         // check whether the denoted abbreviation matches the sibling's abbreviatiopn
324                         if (argDenotationType == Abbreviation && (sibling->abbreviation() && sibling->abbreviation() == *argDenotation)) {
325                             return false;
326                         }
327                         // check whether the denoted name matches the sibling's name
328                         if (sibling->matchesDenotation(argDenotation, argDenotationLength)) {
329                             return false;
330                         }
331                     }
332                 }
333                 if (parentArgument == pathEnd) {
334                     break;
335                 }
336             }
337         }
338 
339         // unknown argument might just be a parameter value of the last argument
340         if (lastArgInLevel && values->size() < lastArgInLevel->requiredValueCount()) {
341             values->emplace_back(abbreviationFound ? argDenotation : *argv);
342             ++index;
343             ++argv;
344             argDenotation = nullptr;
345             continue;
346         }
347 
348         // first value might denote "operation"
349         for (Argument *const arg : args) {
350             if (arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
351                 (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
352                 lastArgDenotation = argv;
353                 ++index;
354                 ++argv;
355                 break;
356             }
357         }
358 
359         // use the first default argument which is not already present if there is still no match
360         if (!matchingArg && (!completionMode || (argv + 1 != end))) {
361             const bool uncombinableMainArgPresent = parentArg ? false : parser.isUncombinableMainArgPresent();
362             for (Argument *const arg : args) {
363                 if (arg->isImplicit() && !arg->isPresent() && !arg->wouldConflictWithArgument()
364                     && (!uncombinableMainArgPresent || !arg->isMainArgument())) {
365                     (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
366                     break;
367                 }
368             }
369         }
370 
371         if (matchingArg) {
372             // an argument matched the specified denotation
373             if (lastArgInLevel == matchingArg) {
374                 break; // break required? -> TODO: add test for this condition
375             }
376 
377             // prepare reading parameter values
378             values = &matchingArg->m_occurrences.back().values;
379 
380             // read sub arguments
381             ++parser.m_actualArgc;
382             lastArg = lastArgInLevel = matchingArg;
383             argDenotation = nullptr;
384             read(lastArg->m_subArgs);
385             argDenotation = nullptr;
386             continue;
387         }
388 
389         // argument denotation is unknown -> handle error
390         if (parentArg) {
391             // continue with parent level
392             return false;
393         }
394         if (completionMode) {
395             // ignore unknown denotation
396             ++index;
397             ++argv;
398             argDenotation = nullptr;
399         } else {
400             switch (parser.m_unknownArgBehavior) {
401             case UnknownArgumentBehavior::Warn:
402                 cerr << Phrases::Warning << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << Phrases::EndFlush;
403                 [[fallthrough]];
404             case UnknownArgumentBehavior::Ignore:
405                 // ignore unknown denotation
406                 ++index;
407                 ++argv;
408                 argDenotation = nullptr;
409                 break;
410             case UnknownArgumentBehavior::Fail:
411                 return false;
412             }
413         }
414     } // while(argv != end)
415     return true;
416 }
417 
418 /*!
419  * \class Wrapper
420  * \brief The Wrapper class is internally used print text which might needs to be wrapped preserving the indentation.
421  * \remarks This class is explicitly *not* part of the public API.
422  */
423 
operator <<(ostream & os,const Wrapper & wrapper)424 ostream &operator<<(ostream &os, const Wrapper &wrapper)
425 {
426     // determine max. number of columns
427     static const TerminalSize termSize(determineTerminalSize());
428     const auto maxColumns = termSize.columns ? termSize.columns : numeric_limits<unsigned short>::max();
429 
430     // print wrapped string considering indentation
431     unsigned short currentCol = wrapper.m_indentation.level;
432     for (const char *currentChar = wrapper.m_str; *currentChar; ++currentChar) {
433         const bool wrappingRequired = currentCol >= maxColumns;
434         if (wrappingRequired || *currentChar == '\n') {
435             // insert newline (TODO: wrap only at end of a word)
436             os << '\n';
437             // print indentation (if enough space)
438             if (wrapper.m_indentation.level < maxColumns) {
439                 os << wrapper.m_indentation;
440                 currentCol = wrapper.m_indentation.level;
441             } else {
442                 currentCol = 0;
443             }
444         }
445         if (*currentChar != '\n' && (!wrappingRequired || *currentChar != ' ')) {
446             os << *currentChar;
447             ++currentCol;
448         }
449     }
450     return os;
451 }
452 
453 CPP_UTILITIES_EXPORT ApplicationInfo applicationInfo;
454 
455 /// \cond
456 
notEmpty(const char * str)457 inline bool notEmpty(const char *str)
458 {
459     return str && *str;
460 }
461 
462 /// \endcond
463 
464 /*!
465  * \class Argument
466  * \brief The Argument class is a wrapper for command line argument information.
467  *
468  * Instances of the Argument class are used as definition when parsing command line
469  * arguments. Arguments can be assigned to an ArgumentParser using
470  * ArgumentParser::setMainArguments() and to another Argument instance using
471  * Argument::setSecondaryArguments().
472  */
473 
474 /*!
475  * \brief Constructs an Argument with the given \a name, \a abbreviation and \a description.
476  *
477  * The \a name and the abbreviation mustn't contain any whitespaces.
478  * The \a name mustn't be empty. The \a abbreviation and the \a description might be empty.
479  */
Argument(const char * name,char abbreviation,const char * description,const char * example)480 Argument::Argument(const char *name, char abbreviation, const char *description, const char *example)
481     : m_name(name)
482     , m_abbreviation(abbreviation)
483     , m_environmentVar(nullptr)
484     , m_description(description)
485     , m_example(example)
486     , m_minOccurrences(0)
487     , m_maxOccurrences(1)
488     , m_requiredValueCount(0)
489     , m_flags(Flags::None)
490     , m_deprecatedBy(nullptr)
491     , m_isMainArg(false)
492     , m_valueCompletionBehavior(ValueCompletionBehavior::PreDefinedValues | ValueCompletionBehavior::Files | ValueCompletionBehavior::Directories
493           | ValueCompletionBehavior::FileSystemIfNoPreDefinedValues)
494     , m_preDefinedCompletionValues(nullptr)
495 {
496 }
497 
498 /*!
499  * \brief Destroys the Argument.
500  */
~Argument()501 Argument::~Argument()
502 {
503 }
504 
505 /*!
506  * \brief Returns the first parameter value of the first occurrence of the argument.
507  * \remarks
508  * - If the argument is not present and the an environment variable has been set
509  *   using setEnvironmentVariable() the value of the specified variable will be returned.
510  * - Returns nullptr if no value is available though.
511  */
firstValue() const512 const char *Argument::firstValue() const
513 {
514     if (!m_occurrences.empty() && !m_occurrences.front().values.empty()) {
515         return m_occurrences.front().values.front();
516     } else if (m_environmentVar) {
517         return getenv(m_environmentVar);
518     } else {
519         return nullptr;
520     }
521 }
522 
523 /*!
524  * \brief Returns the first value like Argument::firstValue() but returns \a fallback instead of nullptr if there's no value.
525  */
firstValueOr(const char * fallback) const526 const char *Argument::firstValueOr(const char *fallback) const
527 {
528     if (const auto *const v = firstValue()) {
529         return v;
530     } else {
531         return fallback;
532     }
533 }
534 
535 /*!
536  * \brief Writes the name, the abbreviation and other information about the Argument to the give ostream.
537  */
printInfo(ostream & os,unsigned char indentation) const538 void Argument::printInfo(ostream &os, unsigned char indentation) const
539 {
540     if (isDeprecated()) {
541         return;
542     }
543     Indentation ident(indentation);
544     os << ident;
545     EscapeCodes::setStyle(os, EscapeCodes::TextAttribute::Bold);
546     if (notEmpty(name())) {
547         if (!denotesOperation()) {
548             os << '-' << '-';
549         }
550         os << name();
551     }
552     if (notEmpty(name()) && abbreviation()) {
553         os << ',' << ' ';
554     }
555     if (abbreviation()) {
556         os << '-' << abbreviation();
557     }
558     EscapeCodes::setStyle(os);
559     if (requiredValueCount()) {
560         unsigned int valueNamesPrint = 0;
561         for (auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
562             os << ' ' << '[' << *i << ']';
563             ++valueNamesPrint;
564         }
565         if (requiredValueCount() == Argument::varValueCount) {
566             os << " ...";
567         } else {
568             for (; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
569                 os << " [value " << (valueNamesPrint + 1) << ']';
570             }
571         }
572     }
573     ident.level += 2;
574     if (notEmpty(description())) {
575         os << '\n' << ident << Wrapper(description(), ident);
576     }
577     if (isRequired()) {
578         os << '\n' << ident << "particularities: mandatory";
579         if (!isMainArgument()) {
580             os << " if parent argument is present";
581         }
582     }
583     if (environmentVariable()) {
584         os << '\n' << ident << "default environment variable: " << Wrapper(environmentVariable(), ident + 30);
585     }
586     os << '\n';
587     bool hasSubArgs = false;
588     for (const auto *const arg : subArguments()) {
589         if (arg->isDeprecated()) {
590             continue;
591         }
592         hasSubArgs = true;
593         arg->printInfo(os, ident.level);
594     }
595     if (notEmpty(example())) {
596         if (ident.level == 2 && hasSubArgs) {
597             os << '\n';
598         }
599         os << ident << "example: " << Wrapper(example(), ident + 9);
600         os << '\n';
601     }
602 }
603 
604 /*!
605  * \brief This function return the first present and uncombinable argument of the given list of arguments.
606  *
607  * The Argument \a except will be ignored.
608  */
firstPresentUncombinableArg(const ArgumentVector & args,const Argument * except)609 Argument *firstPresentUncombinableArg(const ArgumentVector &args, const Argument *except)
610 {
611     for (Argument *arg : args) {
612         if (arg != except && arg->isPresent() && !arg->isCombinable()) {
613             return arg;
614         }
615     }
616     return nullptr;
617 }
618 
619 /*!
620  * \brief Sets the secondary arguments for this arguments.
621  *
622  * The given arguments will be considered as secondary arguments of this argument by the argument parser.
623  * This means that the parser will complain if these arguments are given, but not this argument.
624  * If secondary arguments are labeled as mandatory their parent is also mandatory.
625  *
626  * The Argument does not take ownership. Do not destroy the given arguments as long as they are
627  * used as secondary arguments.
628  *
629  * \sa secondaryArguments()
630  * \sa addSecondaryArgument()
631  * \sa hasSecondaryArguments()
632  */
setSubArguments(const ArgumentInitializerList & secondaryArguments)633 void Argument::setSubArguments(const ArgumentInitializerList &secondaryArguments)
634 {
635     // remove this argument from the parents list of the previous secondary arguments
636     for (Argument *arg : m_subArgs) {
637         arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(), this), arg->m_parents.end());
638     }
639     // assign secondary arguments
640     m_subArgs.assign(secondaryArguments);
641     // add this argument to the parents list of the assigned secondary arguments
642     // and set the parser
643     for (Argument *arg : m_subArgs) {
644         if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
645             arg->m_parents.push_back(this);
646         }
647     }
648 }
649 
650 /*!
651  * \brief Adds \a arg as a secondary argument for this argument.
652  *
653  * \sa secondaryArguments()
654  * \sa setSecondaryArguments()
655  * \sa hasSecondaryArguments()
656  */
addSubArgument(Argument * arg)657 void Argument::addSubArgument(Argument *arg)
658 {
659     if (find(m_subArgs.cbegin(), m_subArgs.cend(), arg) != m_subArgs.cend()) {
660         return;
661     }
662     m_subArgs.push_back(arg);
663     if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
664         arg->m_parents.push_back(this);
665     }
666 }
667 
668 /*!
669  * \brief Returns whether at least one parent argument is present.
670  * \remarks Returns always true for main arguments.
671  */
isParentPresent() const672 bool Argument::isParentPresent() const
673 {
674     if (isMainArgument()) {
675         return true;
676     }
677     for (const Argument *parent : m_parents) {
678         if (parent->isPresent()) {
679             return true;
680         }
681     }
682     return false;
683 }
684 
685 /*!
686  * \brief Checks if this arguments conflicts with other arguments.
687  *
688  * If the argument is in conflict with an other argument this argument will be returned.
689  * Otherwise nullptr will be returned.
690  *
691  * \remarks Conflicts with main arguments aren't considered by this method!
692  */
conflictsWithArgument() const693 Argument *Argument::conflictsWithArgument() const
694 {
695     return isPresent() ? wouldConflictWithArgument() : nullptr;
696 }
697 
698 /*!
699  * \brief Checks if this argument would conflict with other arguments if it was present.
700  *
701  * If the argument is in conflict with an other argument this argument will be returned.
702  * Otherwise nullptr will be returned.
703  *
704  * \remarks Conflicts with main arguments aren't considered by this method!
705  */
wouldConflictWithArgument() const706 Argument *Argument::wouldConflictWithArgument() const
707 {
708     if (isCombinable()) {
709         return nullptr;
710     }
711     for (Argument *parent : m_parents) {
712         for (Argument *sibling : parent->subArguments()) {
713             if (sibling != this && sibling->isPresent() && !sibling->isCombinable()) {
714                 return sibling;
715             }
716         }
717     }
718     return nullptr;
719 }
720 
721 /*!
722  * \brief Returns the first operation argument specified by the user or nullptr if no operation has been specified.
723  * \remarks Only direct sub arguments of this argument are considered.
724  */
specifiedOperation() const725 Argument *Argument::specifiedOperation() const
726 {
727     for (Argument *arg : m_subArgs) {
728         if (arg->denotesOperation() && arg->isPresent()) {
729             return arg;
730         }
731     }
732     return nullptr;
733 }
734 
735 /*!
736  * \brief Resets this argument and all sub arguments recursively.
737  * \sa Argument::reset()
738  */
resetRecursively()739 void Argument::resetRecursively()
740 {
741     for (Argument *arg : m_subArgs) {
742         arg->resetRecursively();
743     }
744     reset();
745 }
746 
747 /*!
748  * \class ArgumentParser
749  * \brief The ArgumentParser class provides a means for handling command line arguments.
750  *
751  * To setup the parser create instances of ApplicationUtilities::Argument to define a
752  * set of known arguments and assign these to the parser using setMainArguments().
753  *
754  * To invoke parsing call parseArgs(). The parser will verify the previously
755  * assigned definitions (and might throw std::invalid_argument) and then parse the
756  * given command line arguments according the definitions (and might throw
757  * CppUtilities::Failure).
758  */
759 
760 /*!
761  * \brief Constructs a new ArgumentParser.
762  */
ArgumentParser()763 ArgumentParser::ArgumentParser()
764     : m_actualArgc(0)
765     , m_executable(nullptr)
766     , m_unknownArgBehavior(UnknownArgumentBehavior::Fail)
767     , m_defaultArg(nullptr)
768     , m_helpArg(*this)
769 {
770 }
771 
772 /*!
773  * \brief Sets the main arguments for the parser. The parser will use these argument definitions
774  *        to when parsing the command line arguments and when printing help information.
775  * \remarks
776  *  - The parser does not take ownership. Do not destroy the arguments as long as they are used as
777  *    main arguments.
778  *  - Sets the first specified argument as default argument if none has been assigned yet and the
779  *    first argument does not require any values or has no mandatory sub arguments.
780  */
setMainArguments(const ArgumentInitializerList & mainArguments)781 void ArgumentParser::setMainArguments(const ArgumentInitializerList &mainArguments)
782 {
783     if (!mainArguments.size()) {
784         m_mainArgs.clear();
785         return;
786     }
787     for (Argument *arg : mainArguments) {
788         arg->m_isMainArg = true;
789     }
790     m_mainArgs.assign(mainArguments);
791     if (m_defaultArg || (*mainArguments.begin())->requiredValueCount()) {
792         return;
793     }
794     bool subArgsRequired = false;
795     for (const Argument *subArg : (*mainArguments.begin())->subArguments()) {
796         if (subArg->isRequired()) {
797             subArgsRequired = true;
798             break;
799         }
800     }
801     if (!subArgsRequired) {
802         m_defaultArg = *mainArguments.begin();
803     }
804 }
805 
806 /*!
807  * \brief Adds the specified \a argument to the main argument.
808  * \remarks
809  * The parser does not take ownership. Do not destroy the argument as long as it is used as
810  * main argument.
811  */
addMainArgument(Argument * argument)812 void ArgumentParser::addMainArgument(Argument *argument)
813 {
814     argument->m_isMainArg = true;
815     m_mainArgs.push_back(argument);
816 }
817 
818 /*!
819  * \brief Prints help text for all assigned arguments.
820  */
printHelp(ostream & os) const821 void ArgumentParser::printHelp(ostream &os) const
822 {
823     EscapeCodes::setStyle(os, EscapeCodes::TextAttribute::Bold);
824     bool wroteLine = false;
825     if (applicationInfo.name && *applicationInfo.name) {
826         os << applicationInfo.name;
827         if (applicationInfo.version && *applicationInfo.version) {
828             os << ',' << ' ';
829         }
830         wroteLine = true;
831     }
832     if (applicationInfo.version && *applicationInfo.version) {
833         os << "version " << applicationInfo.version;
834         wroteLine = true;
835     }
836     if (wroteLine) {
837         os << '\n' << '\n';
838     }
839     EscapeCodes::setStyle(os);
840 
841     if (applicationInfo.description && *applicationInfo.description) {
842         os << applicationInfo.description;
843         wroteLine = true;
844     }
845     if (wroteLine) {
846         os << '\n' << '\n';
847     }
848 
849     if (!m_mainArgs.empty()) {
850         bool hasOperations = false, hasTopLevelOptions = false;
851         for (const Argument *const arg : m_mainArgs) {
852             if (arg->denotesOperation()) {
853                 hasOperations = true;
854             } else if (strcmp(arg->name(), "help")) {
855                 hasTopLevelOptions = true;
856             }
857             if (hasOperations && hasTopLevelOptions) {
858                 break;
859             }
860         }
861 
862         // check whether operations are available
863         if (hasOperations) {
864             // split top-level operations and other configurations
865             os << "Available operations:";
866             for (const Argument *const arg : m_mainArgs) {
867                 if (!arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(), "help")) {
868                     continue;
869                 }
870                 os << '\n';
871                 arg->printInfo(os);
872             }
873             if (hasTopLevelOptions) {
874                 os << "\nAvailable top-level options:";
875                 for (const Argument *const arg : m_mainArgs) {
876                     if (arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(), "help")) {
877                         continue;
878                     }
879                     os << '\n';
880                     arg->printInfo(os);
881                 }
882             }
883         } else {
884             // just show all args if no operations are available
885             os << "Available arguments:";
886             for (const Argument *const arg : m_mainArgs) {
887                 if (arg->isDeprecated() || !strcmp(arg->name(), "help")) {
888                     continue;
889                 }
890                 os << '\n';
891                 arg->printInfo(os);
892             }
893         }
894     }
895 
896     if (!applicationInfo.dependencyVersions.empty()) {
897         os << '\n';
898         auto i = applicationInfo.dependencyVersions.begin(), end = applicationInfo.dependencyVersions.end();
899         os << "Linked against: " << *i;
900         for (++i; i != end; ++i) {
901             os << ',' << ' ' << *i;
902         }
903         os << '\n';
904     }
905 
906     if (applicationInfo.url && *applicationInfo.url) {
907         os << "\nProject website: " << applicationInfo.url << endl;
908     }
909 }
910 
911 /*!
912  * \brief Parses the specified command line arguments.
913  *
914  * The behavior is configurable by specifying the \a behavior argument. See ParseArgumentBehavior for
915  * the options. By default, all options are present.
916  *
917  * \remarks
918  *  - The results are stored in the Argument instances assigned as main arguments and sub arguments.
919  *  - This method will not return in the error case if the ParseArgumentBehavior::ExitOnFailure is present
920  *    (default).
921  *  - This method will not return in case shell completion is requested. This behavior can be altered
922  *    by overriding the exit function via ArgumentParser::setExitFunction() which defaults to &std::exit.
923  * \throws Throws Failure if the specified arguments are invalid and the ParseArgumentBehavior::ExitOnFailure
924  *         flag is *not* present.
925  * \sa readArgs()
926  */
parseArgs(int argc,const char * const * argv,ParseArgumentBehavior behavior)927 void ArgumentParser::parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior)
928 {
929     try {
930         readArgs(argc, argv);
931         if (!argc) {
932             return;
933         }
934         if (behavior & ParseArgumentBehavior::CheckConstraints) {
935             checkConstraints(m_mainArgs);
936         }
937         if (behavior & ParseArgumentBehavior::InvokeCallbacks) {
938             invokeCallbacks(m_mainArgs);
939         }
940     } catch (const ParseError &failure) {
941         if (behavior & ParseArgumentBehavior::ExitOnFailure) {
942             CMD_UTILS_START_CONSOLE;
943             cerr << failure;
944             invokeExit(1);
945         }
946         throw;
947     }
948 }
949 
950 /*!
951  * \brief Parses the specified command line arguments.
952  * \remarks
953  *  - The results are stored in the Argument instances assigned as main arguments and sub arguments.
954  *  - In contrast to parseArgs() this method does not check whether constraints are violated and it
955  *    does not call any callbacks.
956  *  - This method will not return in case shell completion is requested. This behavior can be altered
957  *    by overriding the exit function via ArgumentParser::setExitFunction() which defaults to &std::exit.
958  * \throws Throws Failure if the specified arguments are invalid.
959  * \sa parseArgs()
960  * \deprecated In next major release, this method will be private. parseArgs() can serve the same
961  *             purpose then.
962  */
readArgs(int argc,const char * const * argv)963 void ArgumentParser::readArgs(int argc, const char *const *argv)
964 {
965     CPP_UTILITIES_IF_DEBUG_BUILD(verifyArgs(m_mainArgs);)
966     m_actualArgc = 0;
967 
968     // the first argument is the executable name
969     if (!argc) {
970         m_executable = nullptr;
971         return;
972     }
973     m_executable = *argv;
974 
975     // check for further arguments
976     if (!--argc) {
977         // no arguments specified -> flag default argument as present if one is assigned
978         if (m_defaultArg) {
979             m_defaultArg->m_occurrences.emplace_back(0);
980         }
981         return;
982     }
983 
984     // check for completion mode: if first arg (after executable name) is "--bash-completion-for", bash completion for the following arguments is requested
985     const bool completionMode = !strcmp(*++argv, "--bash-completion-for");
986 
987     // determine the index of the current word for completion and the number of arguments to be passed to ArgumentReader
988     unsigned int currentWordIndex = 0, argcForReader;
989     if (completionMode) {
990         // the first argument after "--bash-completion-for" is the index of the current word
991         try {
992             currentWordIndex = (--argc ? stringToNumber<unsigned int, string>(*(++argv)) : 0);
993             if (argc) {
994                 ++argv;
995                 --argc;
996             }
997         } catch (const ConversionException &) {
998             currentWordIndex = static_cast<unsigned int>(argc - 1);
999         }
1000         argcForReader = min(static_cast<unsigned int>(argc), currentWordIndex + 1);
1001     } else {
1002         argcForReader = static_cast<unsigned int>(argc);
1003     }
1004 
1005     // read specified arguments
1006     ArgumentReader reader(*this, argv, argv + argcForReader, completionMode);
1007     const bool allArgsProcessed(reader.read());
1008     m_noColorArg.apply();
1009 
1010     // fail when not all arguments could be processed, except when in completion mode
1011     if (!completionMode && !allArgsProcessed) {
1012         const auto suggestions(findSuggestions(argc, argv, static_cast<unsigned int>(argc - 1), reader));
1013         throw ParseError(argsToString("The specified argument \"", *reader.argv, "\" is unknown.", suggestions));
1014     }
1015 
1016     // print Bash completion and prevent the application to continue with the regular execution
1017     if (completionMode) {
1018         printBashCompletion(argc, argv, currentWordIndex, reader);
1019         invokeExit(0);
1020     }
1021 }
1022 
1023 /*!
1024  * \brief Resets all Argument instances assigned as mainArguments() and sub arguments.
1025  * \sa Argument::reset()
1026  */
resetArgs()1027 void ArgumentParser::resetArgs()
1028 {
1029     for (Argument *arg : m_mainArgs) {
1030         arg->resetRecursively();
1031     }
1032     m_actualArgc = 0;
1033 }
1034 
1035 /*!
1036  * \brief Returns the first operation argument specified by the user or nullptr if no operation has been specified.
1037  * \remarks Only main arguments are considered. See Argument::specifiedOperation() to check sub arguments of a specific
1038  *          argument.
1039  */
specifiedOperation() const1040 Argument *ArgumentParser::specifiedOperation() const
1041 {
1042     for (Argument *arg : m_mainArgs) {
1043         if (arg->denotesOperation() && arg->isPresent()) {
1044             return arg;
1045         }
1046     }
1047     return nullptr;
1048 }
1049 
1050 /*!
1051  * \brief Checks whether at least one uncombinable main argument is present.
1052  */
isUncombinableMainArgPresent() const1053 bool ArgumentParser::isUncombinableMainArgPresent() const
1054 {
1055     for (const Argument *arg : m_mainArgs) {
1056         if (!arg->isCombinable() && arg->isPresent()) {
1057             return true;
1058         }
1059     }
1060     return false;
1061 }
1062 
1063 #ifdef CPP_UTILITIES_DEBUG_BUILD
1064 /*!
1065  * \brief Verifies the specified \a argument definitions.
1066  *
1067  * Asserts that
1068  *  - The same argument has not been added twice to the same parent.
1069  *  - Only one argument within a parent is default or implicit.
1070  *  - Only main arguments denote operations.
1071  *  - Argument abbreviations are unique within the same level.
1072  *  - Argument names are unique within within the same level.
1073  *
1074  * \remarks
1075  *  - Verifies the sub arguments, too.
1076  *  - For debugging purposes only; hence only used in debug builds.
1077  */
verifyArgs(const ArgumentVector & args)1078 void ArgumentParser::verifyArgs(const ArgumentVector &args)
1079 {
1080     vector<const Argument *> verifiedArgs;
1081     verifiedArgs.reserve(args.size());
1082     vector<char> abbreviations;
1083     abbreviations.reserve(abbreviations.size() + args.size());
1084     vector<const char *> names;
1085     names.reserve(names.size() + args.size());
1086     bool hasImplicit = false;
1087     for (const Argument *arg : args) {
1088         assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
1089         verifiedArgs.push_back(arg);
1090         assert(!arg->isImplicit() || !hasImplicit);
1091         hasImplicit |= arg->isImplicit();
1092         assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
1093         abbreviations.push_back(arg->abbreviation());
1094         assert(!arg->name() || find_if(names.cbegin(), names.cend(), [arg](const char *name) { return !strcmp(arg->name(), name); }) == names.cend());
1095         assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0);
1096         names.emplace_back(arg->name());
1097     }
1098     for (const Argument *arg : args) {
1099         verifyArgs(arg->subArguments());
1100     }
1101 }
1102 #endif
1103 
1104 /*!
1105  * \brief Returns whether \a arg1 should be listed before \a arg2 when
1106  *        printing completion.
1107  *
1108  * Arguments are sorted by name (ascending order). However, all arguments
1109  * denoting an operation are listed before all other arguments.
1110  */
compareArgs(const Argument * arg1,const Argument * arg2)1111 bool compareArgs(const Argument *arg1, const Argument *arg2)
1112 {
1113     if (arg1->denotesOperation() && !arg2->denotesOperation()) {
1114         return true;
1115     } else if (!arg1->denotesOperation() && arg2->denotesOperation()) {
1116         return false;
1117     } else {
1118         return strcmp(arg1->name(), arg2->name()) < 0;
1119     }
1120 }
1121 
1122 /*!
1123  * \brief Inserts the specified \a siblings in the \a target list.
1124  * \remarks Only inserts siblings which could still occur at least once more.
1125  */
insertSiblings(const ArgumentVector & siblings,list<const Argument * > & target)1126 void insertSiblings(const ArgumentVector &siblings, list<const Argument *> &target)
1127 {
1128     bool onlyCombinable = false;
1129     for (const Argument *sibling : siblings) {
1130         if (sibling->isPresent() && !sibling->isCombinable()) {
1131             onlyCombinable = true;
1132             break;
1133         }
1134     }
1135     for (const Argument *sibling : siblings) {
1136         if ((!onlyCombinable || sibling->isCombinable()) && sibling->occurrences() < sibling->maxOccurrences()) {
1137             target.push_back(sibling);
1138         }
1139     }
1140 }
1141 
1142 /*!
1143  * \brief Determines arguments relevant for Bash completion or suggestions in case of typo.
1144  */
determineCompletionInfo(int argc,const char * const * argv,unsigned int currentWordIndex,const ArgumentReader & reader) const1145 ArgumentCompletionInfo ArgumentParser::determineCompletionInfo(
1146     int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1147 {
1148     ArgumentCompletionInfo completion(reader);
1149 
1150     // determine last detected arg
1151     if (completion.lastDetectedArg) {
1152         completion.lastDetectedArgIndex = static_cast<size_t>(reader.lastArgDenotation - argv);
1153         completion.lastDetectedArgPath = completion.lastDetectedArg->path(completion.lastDetectedArg->occurrences() - 1);
1154     }
1155 
1156     // determine last arg, omitting trailing empty args
1157     if (argc) {
1158         completion.lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
1159         completion.lastSpecifiedArg = argv + completion.lastSpecifiedArgIndex;
1160         for (; completion.lastSpecifiedArg >= argv && **completion.lastSpecifiedArg == '\0';
1161              --completion.lastSpecifiedArg, --completion.lastSpecifiedArgIndex)
1162             ;
1163     }
1164 
1165     // just return main arguments if no args detected
1166     if (!completion.lastDetectedArg || !completion.lastDetectedArg->isPresent()) {
1167         completion.nextArgumentOrValue = true;
1168         insertSiblings(m_mainArgs, completion.relevantArgs);
1169         completion.relevantArgs.sort(compareArgs);
1170         return completion;
1171     }
1172 
1173     completion.nextArgumentOrValue = currentWordIndex > completion.lastDetectedArgIndex;
1174     if (!completion.nextArgumentOrValue) {
1175         // since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
1176         completion.relevantArgs.push_back(completion.lastDetectedArg);
1177         completion.relevantArgs.sort(compareArgs);
1178         return completion;
1179     }
1180 
1181     // define function to add parameter values of argument as possible completions
1182     const auto addValueCompletionsForArg = [&completion](const Argument *arg) {
1183         if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) {
1184             completion.relevantPreDefinedValues.push_back(arg);
1185         }
1186         if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues) || !arg->preDefinedCompletionValues()) {
1187             completion.completeFiles = completion.completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
1188             completion.completeDirs = completion.completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
1189         }
1190     };
1191 
1192     // detect number of specified values
1193     auto currentValueCount = completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size();
1194     // ignore values which are specified after the current word
1195     if (currentValueCount) {
1196         const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - completion.lastDetectedArgIndex;
1197         if (currentValueCount > currentWordIndexRelativeToLastDetectedArg) {
1198             currentValueCount -= currentWordIndexRelativeToLastDetectedArg;
1199         } else {
1200             currentValueCount = 0;
1201         }
1202     }
1203 
1204     // add value completions for implicit child if there are no value specified and there are no values required by the
1205     // last detected argument itself
1206     if (!currentValueCount && !completion.lastDetectedArg->requiredValueCount()) {
1207         for (const Argument *child : completion.lastDetectedArg->subArguments()) {
1208             if (child->isImplicit() && child->requiredValueCount()) {
1209                 addValueCompletionsForArg(child);
1210                 break;
1211             }
1212         }
1213     }
1214 
1215     // add value completions for last argument if there are further values required
1216     if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1217         || (currentValueCount < completion.lastDetectedArg->requiredValueCount())) {
1218         addValueCompletionsForArg(completion.lastDetectedArg);
1219     }
1220 
1221     if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1222         || completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size()
1223             >= completion.lastDetectedArg->requiredValueCount()) {
1224         // sub arguments of the last arg are possible completions
1225         for (const Argument *subArg : completion.lastDetectedArg->subArguments()) {
1226             if (subArg->occurrences() < subArg->maxOccurrences()) {
1227                 completion.relevantArgs.push_back(subArg);
1228             }
1229         }
1230 
1231         // siblings of parents are possible completions as well
1232         for (auto parentArgument = completion.lastDetectedArgPath.crbegin(), end = completion.lastDetectedArgPath.crend();; ++parentArgument) {
1233             insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, completion.relevantArgs);
1234             if (parentArgument == end) {
1235                 break;
1236             }
1237         }
1238     }
1239 
1240     return completion;
1241 }
1242 
1243 /*!
1244  * \brief Returns the suggestion string printed in error case due to unknown arguments.
1245  */
findSuggestions(int argc,const char * const * argv,unsigned int cursorPos,const ArgumentReader & reader) const1246 string ArgumentParser::findSuggestions(int argc, const char *const *argv, unsigned int cursorPos, const ArgumentReader &reader) const
1247 {
1248     // determine completion info
1249     const auto completionInfo(determineCompletionInfo(argc, argv, cursorPos, reader));
1250 
1251     // determine the unknown/misspelled argument
1252     const auto *unknownArg(*reader.argv);
1253     auto unknownArgSize(strlen(unknownArg));
1254     // -> refuse suggestions for long args to prevent huge memory allocation for Damerau-Levenshtein algo
1255     if (unknownArgSize > 16) {
1256         return string();
1257     }
1258     // -> remove dashes since argument names internally don't have them
1259     if (unknownArgSize >= 2 && unknownArg[0] == '-' && unknownArg[1] == '-') {
1260         unknownArg += 2;
1261         unknownArgSize -= 2;
1262     }
1263 
1264     // find best suggestions limiting the results to 2
1265     multiset<ArgumentSuggestion> bestSuggestions;
1266     // -> consider relevant arguments
1267     for (const Argument *const arg : completionInfo.relevantArgs) {
1268         ArgumentSuggestion(unknownArg, unknownArgSize, arg->name(), !arg->denotesOperation()).addTo(bestSuggestions, 2);
1269     }
1270     // -> consider relevant values
1271     for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1272         if (!arg->preDefinedCompletionValues()) {
1273             continue;
1274         }
1275         for (const char *i = arg->preDefinedCompletionValues(); *i; ++i) {
1276             const char *const wordStart(i);
1277             const char *wordEnd(wordStart + 1);
1278             for (; *wordEnd && *wordEnd != ' '; ++wordEnd)
1279                 ;
1280             ArgumentSuggestion(unknownArg, unknownArgSize, wordStart, static_cast<size_t>(wordEnd - wordStart), false).addTo(bestSuggestions, 2);
1281             i = wordEnd;
1282         }
1283     }
1284 
1285     // format suggestion
1286     string suggestionStr;
1287     if (const auto suggestionCount = bestSuggestions.size()) {
1288         // allocate memory
1289         size_t requiredSize = 15;
1290         for (const auto &suggestion : bestSuggestions) {
1291             requiredSize += suggestion.suggestionSize + 2;
1292             if (suggestion.hasDashPrefix) {
1293                 requiredSize += 2;
1294             }
1295         }
1296         suggestionStr.reserve(requiredSize);
1297 
1298         // add each suggestion to end up with something like "Did you mean status (1), pause (3), cat (4), edit (5) or rescan-all (8)?"
1299         suggestionStr += "\nDid you mean ";
1300         size_t i = 0;
1301         for (const auto &suggestion : bestSuggestions) {
1302             if (++i == suggestionCount && suggestionCount != 1) {
1303                 suggestionStr += " or ";
1304             } else if (i > 1) {
1305                 suggestionStr += ", ";
1306             }
1307             if (suggestion.hasDashPrefix) {
1308                 suggestionStr += "--";
1309             }
1310             suggestionStr.append(suggestion.suggestion, suggestion.suggestionSize);
1311         }
1312         suggestionStr += '?';
1313     }
1314     return suggestionStr;
1315 }
1316 
1317 /*!
1318  * \brief Prints the bash completion for the specified arguments and the specified \a lastPath.
1319  * \remarks Arguments must have been parsed before with readSpecifiedArgs(). When calling this method, completionMode must
1320  *          be set to true.
1321  */
printBashCompletion(int argc,const char * const * argv,unsigned int currentWordIndex,const ArgumentReader & reader) const1322 void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1323 {
1324     // determine completion info and sort relevant arguments
1325     const auto completionInfo([&] {
1326         auto clutteredCompletionInfo(determineCompletionInfo(argc, argv, currentWordIndex, reader));
1327         clutteredCompletionInfo.relevantArgs.sort(compareArgs);
1328         return clutteredCompletionInfo;
1329     }());
1330 
1331     // read the "opening" (started but not finished argument denotation)
1332     const char *opening = nullptr;
1333     string compoundOpening;
1334     size_t openingLen = 0, compoundOpeningStartLen = 0;
1335     unsigned char openingDenotationType = Value;
1336     if (argc && completionInfo.nextArgumentOrValue) {
1337         if (currentWordIndex < static_cast<unsigned int>(argc)) {
1338             opening = argv[currentWordIndex];
1339             // For some reason completions for eg. "set --values disk=1 tag=a" are split so the
1340             // equation sign is an own argument ("set --values disk = 1 tag = a").
1341             // This is not how values are treated by the argument parser. Hence the opening
1342             // must be joined again. In this case only the part after the equation sign needs to be
1343             // provided for completion so compoundOpeningStartLen is set to number of characters to skip.
1344             const size_t minCurrentWordIndex = (completionInfo.lastDetectedArg ? completionInfo.lastDetectedArgIndex : 0);
1345             if (currentWordIndex > minCurrentWordIndex && !strcmp(opening, "=")) {
1346                 compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
1347                 compoundOpening = argv[currentWordIndex];
1348                 compoundOpening += '=';
1349             } else if (currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1], "=")) {
1350                 compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
1351                 compoundOpening = argv[currentWordIndex];
1352                 compoundOpening += '=';
1353                 compoundOpening += opening;
1354             }
1355             if (!compoundOpening.empty()) {
1356                 opening = compoundOpening.data();
1357             }
1358         } else {
1359             opening = *completionInfo.lastSpecifiedArg;
1360         }
1361         if (*opening == '-') {
1362             ++opening;
1363             ++openingDenotationType;
1364             if (*opening == '-') {
1365                 ++opening;
1366                 ++openingDenotationType;
1367             }
1368         }
1369         openingLen = strlen(opening);
1370     }
1371 
1372     // print "COMPREPLY" bash array
1373     cout << "COMPREPLY=(";
1374     // -> completions for parameter values
1375     bool noWhitespace = false;
1376     for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1377         if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::InvokeCallback && arg->m_callbackFunction) {
1378             arg->m_callbackFunction(arg->isPresent() ? arg->m_occurrences.front() : ArgumentOccurrence(Argument::varValueCount));
1379         }
1380         if (!arg->preDefinedCompletionValues()) {
1381             continue;
1382         }
1383         const bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
1384         if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1385             if (openingDenotationType != Value) {
1386                 continue;
1387             }
1388             bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
1389             size_t wordIndex = 0;
1390             for (const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) {
1391                 if (wordStart) {
1392                     const char *i1 = i, *i2 = opening;
1393                     for (; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2)
1394                         ;
1395                     if ((ok = (i2 == end))) {
1396                         cout << '\'';
1397                     }
1398                     wordStart = false;
1399                     wordIndex = 0;
1400                 } else if ((wordStart = (*i == ' ') || (*i == '\n'))) {
1401                     equationSignAlreadyPresent = false;
1402                     if (ok) {
1403                         cout << '\'' << ' ';
1404                     }
1405                     ++i;
1406                     continue;
1407                 } else if (*i == '=') {
1408                     equationSignAlreadyPresent = true;
1409                 }
1410                 if (!ok) {
1411                     ++i;
1412                     continue;
1413                 }
1414                 if (!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
1415                     if (*i == '\'') {
1416                         cout << "'\"'\"'";
1417                     } else {
1418                         cout << *i;
1419                     }
1420                 }
1421                 ++i;
1422                 ++wordIndex;
1423                 switch (*i) {
1424                 case ' ':
1425                 case '\n':
1426                 case '\0':
1427                     if (appendEquationSign && !equationSignAlreadyPresent) {
1428                         cout << '=';
1429                         noWhitespace = true;
1430                         equationSignAlreadyPresent = false;
1431                     }
1432                     if (*i == '\0') {
1433                         cout << '\'';
1434                     }
1435                 }
1436             }
1437             cout << ' ';
1438         } else if (const char *i = arg->preDefinedCompletionValues()) {
1439             bool equationSignAlreadyPresent = false;
1440             cout << '\'';
1441             while (*i) {
1442                 if (*i == '\'') {
1443                     cout << "'\"'\"'";
1444                 } else {
1445                     cout << *i;
1446                 }
1447                 switch (*(++i)) {
1448                 case '=':
1449                     equationSignAlreadyPresent = true;
1450                     break;
1451                 case ' ':
1452                 case '\n':
1453                 case '\0':
1454                     if (appendEquationSign && !equationSignAlreadyPresent) {
1455                         cout << '=';
1456                         equationSignAlreadyPresent = false;
1457                     }
1458                     if (*i != '\0') {
1459                         cout << '\'';
1460                         if (*(++i)) {
1461                             cout << ' ' << '\'';
1462                         }
1463                     }
1464                 }
1465             }
1466             cout << '\'' << ' ';
1467         }
1468     }
1469     // -> completions for further arguments
1470     for (const Argument *const arg : completionInfo.relevantArgs) {
1471         if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1472             switch (openingDenotationType) {
1473             case Value:
1474                 if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1475                     continue;
1476                 }
1477                 break;
1478             case Abbreviation:
1479                 break;
1480             case FullName:
1481                 if (strncmp(arg->name(), opening, openingLen)) {
1482                     continue;
1483                 }
1484             }
1485         }
1486 
1487         if (opening && openingDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1488             // TODO: add test for this case
1489             cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
1490         } else if (completionInfo.lastDetectedArg && reader.argDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1491             if (reader.argv == reader.end) {
1492                 cout << '\'' << *(reader.argv - 1) << '\'' << ' ';
1493             }
1494         } else if (arg->denotesOperation()) {
1495             cout << '\'' << arg->name() << '\'' << ' ';
1496         } else {
1497             cout << '\'' << '-' << '-' << arg->name() << '\'' << ' ';
1498         }
1499     }
1500     // -> completions for files and dirs
1501     // -> if there's already an "opening", determine the dir part and the file part
1502     string actualDir, actualFile;
1503     bool haveFileOrDirCompletions = false;
1504     if (argc && currentWordIndex == completionInfo.lastSpecifiedArgIndex && opening) {
1505         // the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
1506         string unescapedOpening(opening);
1507         findAndReplace<string>(unescapedOpening, "\\ ", " ");
1508         findAndReplace<string>(unescapedOpening, "\\,", ",");
1509         findAndReplace<string>(unescapedOpening, "\\[", "[");
1510         findAndReplace<string>(unescapedOpening, "\\]", "]");
1511         findAndReplace<string>(unescapedOpening, "\\!", "!");
1512         findAndReplace<string>(unescapedOpening, "\\#", "#");
1513         findAndReplace<string>(unescapedOpening, "\\$", "$");
1514         findAndReplace<string>(unescapedOpening, "\\'", "'");
1515         findAndReplace<string>(unescapedOpening, "\\\"", "\"");
1516         findAndReplace<string>(unescapedOpening, "\\\\", "\\");
1517         // determine the "directory" part
1518         string dir = directory(unescapedOpening);
1519         if (dir.empty()) {
1520             actualDir = ".";
1521         } else {
1522             if (dir[0] == '\"' || dir[0] == '\'') {
1523                 dir.erase(0, 1);
1524             }
1525             if (dir.size() > 1 && (dir[dir.size() - 2] == '\"' || dir[dir.size() - 2] == '\'')) {
1526                 dir.erase(dir.size() - 2, 1);
1527             }
1528             actualDir = move(dir);
1529         }
1530         // determine the "file" part
1531         string file = fileName(unescapedOpening);
1532         if (file[0] == '\"' || file[0] == '\'') {
1533             file.erase(0, 1);
1534         }
1535         if (file.size() > 1 && (file[file.size() - 2] == '\"' || file[file.size() - 2] == '\'')) {
1536             file.erase(file.size() - 2, 1);
1537         }
1538         actualFile = move(file);
1539     }
1540 
1541     // -> completion for files and dirs
1542 #ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
1543     if (completionInfo.completeFiles || completionInfo.completeDirs) {
1544         try {
1545             const auto replace = "'"s, with = "'\"'\"'"s;
1546             const auto useActualDir = argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening;
1547             const auto dirEntries = [&] {
1548                 filesystem::directory_iterator i;
1549                 if (useActualDir) {
1550                     i = filesystem::directory_iterator(actualDir);
1551                     findAndReplace(actualDir, replace, with);
1552                 } else {
1553                     i = filesystem::directory_iterator(".");
1554                 }
1555                 return i;
1556             }();
1557             for (const auto &dirEntry : dirEntries) {
1558                 if (!completionInfo.completeDirs && dirEntry.is_directory()) {
1559                     continue;
1560                 }
1561                 if (!completionInfo.completeFiles && !dirEntry.is_directory()) {
1562                     continue;
1563                 }
1564                 auto dirEntryName = dirEntry.path().filename().string();
1565                 auto hasStartingQuote = false;
1566                 if (useActualDir) {
1567                     if (!startsWith(dirEntryName, actualFile)) {
1568                         continue;
1569                     }
1570                     cout << '\'';
1571                     hasStartingQuote = true;
1572                     if (actualDir != ".") {
1573                         cout << actualDir;
1574                     }
1575                 }
1576                 findAndReplace(dirEntryName, replace, with);
1577                 if (!hasStartingQuote) {
1578                     cout << '\'';
1579                 }
1580                 cout << dirEntryName << '\'' << ' ';
1581                 haveFileOrDirCompletions = true;
1582             }
1583         } catch (const filesystem::filesystem_error &) {
1584             // ignore filesystem errors; there's no good way to report errors when printing bash completion
1585         }
1586     }
1587 #endif
1588     cout << ')';
1589 
1590     // ensure file or dir completions are formatted appropriately
1591     if (haveFileOrDirCompletions) {
1592         cout << "; compopt -o filenames";
1593     }
1594 
1595     // ensure trailing whitespace is omitted
1596     if (noWhitespace) {
1597         cout << "; compopt -o nospace";
1598     }
1599 
1600     cout << endl;
1601 }
1602 
1603 /*!
1604  * \brief Checks the constrains of the specified \a args.
1605  * \remarks Checks the constraints of sub arguments, too.
1606  */
checkConstraints(const ArgumentVector & args)1607 void ArgumentParser::checkConstraints(const ArgumentVector &args)
1608 {
1609     for (const Argument *arg : args) {
1610         const auto occurrences = arg->occurrences();
1611         if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1612             throw ParseError(argsToString("The argument \"", arg->name(), "\" mustn't be specified more than ", arg->maxOccurrences(),
1613                 (arg->maxOccurrences() == 1 ? " time." : " times.")));
1614         }
1615         if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1616             throw ParseError(argsToString("The argument \"", arg->name(), "\" must be specified at least ", arg->minOccurrences(),
1617                 (arg->minOccurrences() == 1 ? " time." : " times.")));
1618         }
1619         Argument *conflictingArgument = nullptr;
1620         if (arg->isMainArgument()) {
1621             if (!arg->isCombinable() && arg->isPresent()) {
1622                 conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
1623             }
1624         } else {
1625             conflictingArgument = arg->conflictsWithArgument();
1626         }
1627         if (conflictingArgument) {
1628             throw ParseError(argsToString("The argument \"", conflictingArgument->name(), "\" can not be combined with \"", arg->name(), "\"."));
1629         }
1630         for (size_t i = 0; i != occurrences; ++i) {
1631             if (arg->allRequiredValuesPresent(i)) {
1632                 continue;
1633             }
1634             stringstream ss(stringstream::in | stringstream::out);
1635             ss << "Not all parameters for argument \"" << arg->name() << "\" ";
1636             if (i) {
1637                 ss << " (" << (i + 1) << " occurrence) ";
1638             }
1639             ss << "provided. You have to provide the following parameters:";
1640             size_t valueNamesPrint = 0;
1641             for (const auto &name : arg->m_valueNames) {
1642                 ss << ' ' << name;
1643                 ++valueNamesPrint;
1644             }
1645             if (arg->m_requiredValueCount != Argument::varValueCount) {
1646                 while (valueNamesPrint < arg->m_requiredValueCount) {
1647                     ss << "\nvalue " << (++valueNamesPrint);
1648                 }
1649             }
1650             throw ParseError(ss.str());
1651         }
1652 
1653         // check constraints of sub arguments recursively
1654         checkConstraints(arg->m_subArgs);
1655     }
1656 }
1657 
1658 /*!
1659  * \brief Invokes the callbacks for the specified \a args.
1660  * \remarks
1661  *  - Checks the callbacks for sub arguments, too.
1662  *  - Invokes the assigned callback methods for each occurrence of
1663  *    the argument.
1664  */
invokeCallbacks(const ArgumentVector & args)1665 void ArgumentParser::invokeCallbacks(const ArgumentVector &args)
1666 {
1667     for (const Argument *arg : args) {
1668         // invoke the callback for each occurrence of the argument
1669         if (arg->m_callbackFunction) {
1670             for (const auto &occurrence : arg->m_occurrences) {
1671                 arg->m_callbackFunction(occurrence);
1672             }
1673         }
1674         // invoke the callbacks for sub arguments recursively
1675         invokeCallbacks(arg->m_subArgs);
1676     }
1677 }
1678 
1679 /*!
1680  * \brief Exits using the assigned function or std::exit().
1681  */
invokeExit(int code)1682 void ArgumentParser::invokeExit(int code)
1683 {
1684     if (m_exitFunction) {
1685         m_exitFunction(code);
1686         return;
1687     }
1688     std::exit(code);
1689 }
1690 
1691 /*!
1692  * \class HelpArgument
1693  * \brief The HelpArgument class prints help information for an argument parser
1694  *        when present (--help, -h).
1695  */
1696 
1697 /*!
1698  * \brief Constructs a new help argument for the specified parser.
1699  */
HelpArgument(ArgumentParser & parser)1700 HelpArgument::HelpArgument(ArgumentParser &parser)
1701     : Argument("help", 'h', "shows this information")
1702 {
1703     setCallback([&parser](const ArgumentOccurrence &) {
1704         CMD_UTILS_START_CONSOLE;
1705         parser.printHelp(cout);
1706     });
1707 }
1708 
1709 /*!
1710  * \class OperationArgument
1711  * \brief The OperationArgument class is an Argument where denotesOperation() is true by default.
1712  */
1713 
1714 /*!
1715  * \class ConfigValueArgument
1716  * \brief The ConfigValueArgument class is an Argument where setCombinable() is true by default.
1717  * \sa ConfigValueArgument::ConfigValueArgument()
1718  */
1719 
1720 /*!
1721  * \class NoColorArgument
1722  * \brief The NoColorArgument class allows to specify whether use of escape codes or similar technique to provide formatted output
1723  *        on the terminal should be enabled/disabled.
1724  *
1725  * This argument will either prevent or explicitly allow the use of escape codes or similar technique to provide formatted output
1726  * on the terminal. More explicitly, the argument will always allow to negate the default value of EscapeCodes::enabled which can be
1727  * configured at build time by setting the CMake variable ENABLE_ESCAPE_CODES_BY_DEFAULT.
1728  *
1729  * \remarks
1730  * - Only the first instance is considered for actually altering the value of EscapeCodes::enabled so it makes no sense to
1731  *   instantiate this class multiple times.
1732  * - It is ensure that EscapeCodes::enabled will be set before any callback functions are invoked and even in the error case (if
1733  *   the error doesn't prevent the argument from being detected). Hence this feature is implemented via NoColorArgument::apply()
1734  *   rather than the usual callback mechanism.
1735  *
1736  * \sa NoColorArgument::NoColorArgument(), EscapeCodes::enabled
1737  */
1738 
1739 /*!
1740  * \brief Constructs a new NoColorArgument argument.
1741  * \remarks This will also set EscapeCodes::enabled to the value of the environment variable ENABLE_ESCAPE_CODES.
1742  */
NoColorArgument()1743 NoColorArgument::NoColorArgument()
1744 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1745     : Argument("no-color", '\0', "disables formatted/colorized output")
1746 #else
1747     : Argument("enable-color", '\0', "enables formatted/colorized output")
1748 #endif
1749 {
1750     setCombinable(true);
1751 
1752     // set the environment variable (not directly used and just assigned for printing help)
1753     setEnvironmentVariable("ENABLE_ESCAPE_CODES");
1754 
1755     // initialize EscapeCodes::enabled from environment variable
1756     const auto escapeCodesEnabled = isEnvVariableSet(environmentVariable());
1757     if (escapeCodesEnabled.has_value()) {
1758         EscapeCodes::enabled = escapeCodesEnabled.value();
1759     }
1760 }
1761 
1762 /*!
1763  * \brief Sets EscapeCodes::enabled according to the presence of the first instantiation of NoColorArgument.
1764  */
apply() const1765 void NoColorArgument::apply() const
1766 {
1767     if (isPresent()) {
1768 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1769         EscapeCodes::enabled = false;
1770 #else
1771         EscapeCodes::enabled = true;
1772 #endif
1773     }
1774 }
1775 
1776 /*!
1777  * \brief Throws a Failure for the current instance and the specified \a argumentPath.
1778  */
throwFailure(const std::vector<Argument * > & argumentPath) const1779 void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(const std::vector<Argument *> &argumentPath) const
1780 {
1781     throw ParseError(argumentPath.empty()
1782             ? argsToString("Conversion of top-level value \"", valueToConvert, "\" to type \"", targetTypeName, "\" failed: ", errorMessage)
1783             : argsToString("Conversion of value \"", valueToConvert, "\" (for argument --", argumentPath.back()->name(), ") to type \"",
1784                 targetTypeName, "\" failed: ", errorMessage));
1785 }
1786 
1787 /*!
1788  * \brief Throws a Failure for insufficient number of values.
1789  */
throwNumberOfValuesNotSufficient(unsigned long valuesToConvert) const1790 void ArgumentOccurrence::throwNumberOfValuesNotSufficient(unsigned long valuesToConvert) const
1791 {
1792     throw ParseError(path.empty()
1793             ? argsToString("Expected ", valuesToConvert, " top-level values to be present but only ", values.size(), " have been specified.")
1794             : argsToString("Expected ", valuesToConvert, " values for argument --", path.back()->name(), " to be present but only ", values.size(),
1795                 " have been specified."));
1796 }
1797 
1798 } // namespace CppUtilities
1799