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