C++ Utilities 5.17.0
Useful C++ classes and routines such as argument parser, IO and conversion utilities
argumentparser.cpp
Go to the documentation of this file.
1#include "./argumentparser.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
24using namespace std;
25using namespace std::placeholders;
26using namespace std::literals;
27using namespace CppUtilities::EscapeCodes;
28
33namespace CppUtilities {
34
38std::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
59enum ArgumentDenotationType : unsigned char {
60 Value = 0,
62 FullName = 2
63};
64
70
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
87 : lastDetectedArg(reader.lastArg)
88{
89}
90
92struct 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
105ArgumentSuggestion::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
113ArgumentSuggestion::ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool isOperation)
114 : ArgumentSuggestion(unknownArg, unknownArgSize, suggestion, strlen(suggestion), isOperation)
115{
116}
117
118bool ArgumentSuggestion::operator<(const ArgumentSuggestion &other) const
119{
120 return editingDistance < other.editingDistance;
121}
122
123void 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}
134
147ArgumentReader::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
162ArgumentReader &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
177{
178 return read(args);
179}
180
184bool 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
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) || (lastArgInLevel->flags() & Argument::Flags::Greedy))
210 && values->size() < lastArgInLevel->requiredValueCount()) {
211 // read arg as value and continue with next arg
212 values->emplace_back(argDenotation ? argDenotation : *argv);
213 ++index;
214 ++argv;
215 argDenotation = nullptr;
216 continue;
217 }
218
219 // determine how denotation must be processed
220 bool abbreviationFound = false;
221 if (argDenotation) {
222 // continue reading children for abbreviation denotation already detected
223 abbreviationFound = false;
225 } else {
226 // determine denotation type
228 if (!*argDenotation && (!lastArgInLevel || values->size() >= lastArgInLevel->requiredValueCount())) {
229 // skip empty arguments
230 ++index;
231 ++argv;
232 argDenotation = nullptr;
233 continue;
234 }
235 abbreviationFound = false;
237 if (*argDenotation == '-') {
240 if (*argDenotation == '-') {
243 }
244 }
245 }
246
247 // try to find matching Argument instance
248 Argument *matchingArg = nullptr;
249 if (argDenotationType != Value) {
250 // determine actual denotation length (everything before equation sign)
251 const char *const equationPos = strchr(argDenotation, '=');
252 const auto argDenotationLength = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation);
253
254 // loop through each "part" of the denotation
255 // names are read at once, but for abbreviations each character is considered individually
256 for (; argDenotationLength; matchingArg = nullptr) {
257 // search for arguments by abbreviation or name depending on the previously determined denotation type
259 for (Argument *const arg : args) {
260 if (arg->abbreviation() && arg->abbreviation() == *argDenotation) {
261 matchingArg = arg;
262 abbreviationFound = true;
263 break;
264 }
265 }
266 } else {
267 for (Argument *const arg : args) {
268 if (arg->matchesDenotation(argDenotation, argDenotationLength)) {
269 matchingArg = arg;
270 break;
271 }
272 }
273 }
274 if (!matchingArg) {
275 break;
276 }
277
278 // an argument matched the specified denotation so add an occurrence
279 matchingArg->m_occurrences.emplace_back(index, parentPath, parentArg);
280
281 // prepare reading parameter values
282 values = &matchingArg->m_occurrences.back().values;
283
284 // read value after equation sign
285 if ((argDenotationType != Abbreviation && equationPos) || (++argDenotation == equationPos)) {
286 values->push_back(equationPos + 1);
287 argDenotation = nullptr;
288 }
289
290 // read sub arguments, distinguish whether further abbreviations follow
291 ++index;
292 ++parser.m_actualArgc;
293 lastArg = lastArgInLevel = matchingArg;
296 // no further abbreviations follow -> read sub args for next argv
297 ++argv;
298 argDenotation = nullptr;
299 read(lastArg->m_subArgs);
300 argDenotation = nullptr;
301 break;
302 } else {
303 // further abbreviations follow -> remember current arg value
304 const char *const *const currentArgValue = argv;
305 // don't increment argv, keep processing outstanding chars of argDenotation
306 read(lastArg->m_subArgs);
307 // stop further processing if the denotation has been consumed or even the next value has already been loaded
308 if (!argDenotation || currentArgValue != argv) {
309 argDenotation = nullptr;
310 break;
311 }
312 }
313 }
314
315 // continue with next arg if we've got a match already
316 if (matchingArg) {
317 continue;
318 }
319
320 // unknown argument might be a sibling of the parent element
321 for (auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend();; ++parentArgument) {
322 for (Argument *const sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() : parser.m_mainArgs)) {
323 if (sibling->occurrences() < sibling->maxOccurrences()) {
324 // check whether the denoted abbreviation matches the sibling's abbreviatiopn
325 if (argDenotationType == Abbreviation && (sibling->abbreviation() && sibling->abbreviation() == *argDenotation)) {
326 return false;
327 }
328 // check whether the denoted name matches the sibling's name
329 if (sibling->matchesDenotation(argDenotation, argDenotationLength)) {
330 return false;
331 }
332 }
333 }
334 if (parentArgument == pathEnd) {
335 break;
336 }
337 }
338 }
339
340 // unknown argument might just be a parameter value of the last argument
341 if (lastArgInLevel && values->size() < lastArgInLevel->requiredValueCount()) {
342 values->emplace_back(abbreviationFound ? argDenotation : *argv);
343 ++index;
344 ++argv;
345 argDenotation = nullptr;
346 continue;
347 }
348
349 // first value might denote "operation"
350 for (Argument *const arg : args) {
351 if (arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
352 (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
354 ++index;
355 ++argv;
356 break;
357 }
358 }
359
360 // use the first default argument which is not already present if there is still no match
361 if (!matchingArg && (!completionMode || (argv + 1 != end))) {
362 const bool uncombinableMainArgPresent = parentArg ? false : parser.isUncombinableMainArgPresent();
363 for (Argument *const arg : args) {
364 if (arg->isImplicit() && !arg->isPresent() && !arg->wouldConflictWithArgument()
365 && (!uncombinableMainArgPresent || !arg->isMainArgument())) {
366 (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
367 break;
368 }
369 }
370 }
371
372 if (matchingArg) {
373 // an argument matched the specified denotation
374 if (lastArgInLevel == matchingArg) {
375 break; // break required? -> TODO: add test for this condition
376 }
377
378 // prepare reading parameter values
379 values = &matchingArg->m_occurrences.back().values;
380
381 // read sub arguments
382 ++parser.m_actualArgc;
383 lastArg = lastArgInLevel = matchingArg;
384 argDenotation = nullptr;
385 read(lastArg->m_subArgs);
386 argDenotation = nullptr;
387 continue;
388 }
389
390 // argument denotation is unknown -> handle error
391 if (parentArg) {
392 // continue with parent level
393 return false;
394 }
395 if (completionMode) {
396 // ignore unknown denotation
397 ++index;
398 ++argv;
399 argDenotation = nullptr;
400 } else {
401 switch (parser.m_unknownArgBehavior) {
403 cerr << Phrases::Warning << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << Phrases::EndFlush;
404 [[fallthrough]];
406 // ignore unknown denotation
407 ++index;
408 ++argv;
409 argDenotation = nullptr;
410 break;
412 return false;
413 }
414 }
415 } // while(argv != end)
416 return true;
417}
418
425ostream &operator<<(ostream &os, const Wrapper &wrapper)
426{
427 // determine max. number of columns
428 static const TerminalSize termSize(determineTerminalSize());
429 const auto maxColumns = termSize.columns ? termSize.columns : numeric_limits<unsigned short>::max();
430
431 // print wrapped string considering indentation
432 unsigned short currentCol = wrapper.m_indentation.level;
433 for (const char *currentChar = wrapper.m_str; *currentChar; ++currentChar) {
434 const bool wrappingRequired = currentCol >= maxColumns;
435 if (wrappingRequired || *currentChar == '\n') {
436 // insert newline (TODO: wrap only at end of a word)
437 os << '\n';
438 // print indentation (if enough space)
439 if (wrapper.m_indentation.level < maxColumns) {
440 os << wrapper.m_indentation;
441 currentCol = wrapper.m_indentation.level;
442 } else {
443 currentCol = 0;
444 }
445 }
446 if (*currentChar != '\n' && (!wrappingRequired || *currentChar != ' ')) {
447 os << *currentChar;
448 ++currentCol;
449 }
450 }
451 return os;
452}
453
455
457
458inline bool notEmpty(const char *str)
459{
460 return str && *str;
461}
462
464
481Argument::Argument(const char *name, char abbreviation, const char *description, const char *example)
482 : m_name(name)
483 , m_abbreviation(abbreviation)
484 , m_environmentVar(nullptr)
485 , m_description(description)
486 , m_example(example)
487 , m_minOccurrences(0)
488 , m_maxOccurrences(1)
489 , m_requiredValueCount(0)
490 , m_flags(Flags::None)
491 , m_deprecatedBy(nullptr)
492 , m_isMainArg(false)
495 , m_preDefinedCompletionValues(nullptr)
496{
497}
498
503{
504}
505
513const char *Argument::firstValue() const
514{
515 if (!m_occurrences.empty() && !m_occurrences.front().values.empty()) {
516 return m_occurrences.front().values.front();
517 } else if (m_environmentVar) {
518 return getenv(m_environmentVar);
519 } else {
520 return nullptr;
521 }
522}
523
527const char *Argument::firstValueOr(const char *fallback) const
528{
529 if (const auto *const v = firstValue()) {
530 return v;
531 } else {
532 return fallback;
533 }
534}
535
539void Argument::printInfo(ostream &os, unsigned char indentation) const
540{
541 if (isDeprecated()) {
542 return;
543 }
544 Indentation ident(indentation);
545 os << ident;
547 if (notEmpty(name())) {
548 if (!denotesOperation()) {
549 os << '-' << '-';
550 }
551 os << name();
552 }
553 if (notEmpty(name()) && abbreviation()) {
554 os << ',' << ' ';
555 }
556 if (abbreviation()) {
557 os << '-' << abbreviation();
558 }
560 if (requiredValueCount()) {
561 unsigned int valueNamesPrint = 0;
562 for (auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
563 os << ' ' << '[' << *i << ']';
564 ++valueNamesPrint;
565 }
567 os << " ...";
568 } else {
569 for (; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
570 os << " [value " << (valueNamesPrint + 1) << ']';
571 }
572 }
573 }
574 ident.level += 2;
575 if (notEmpty(description())) {
576 os << '\n' << ident << Wrapper(description(), ident);
577 }
578 if (isRequired()) {
579 os << '\n' << ident << "particularities: mandatory";
580 if (!isMainArgument()) {
581 os << " if parent argument is present";
582 }
583 }
584 if (environmentVariable()) {
585 os << '\n' << ident << "default environment variable: " << Wrapper(environmentVariable(), ident + 30);
586 }
587 os << '\n';
588 bool hasSubArgs = false;
589 for (const auto *const arg : subArguments()) {
590 if (arg->isDeprecated()) {
591 continue;
592 }
593 hasSubArgs = true;
594 arg->printInfo(os, ident.level);
595 }
596 if (notEmpty(example())) {
597 if (ident.level == 2 && hasSubArgs) {
598 os << '\n';
599 }
600 os << ident << "example: " << Wrapper(example(), ident + 9);
601 os << '\n';
602 }
603}
604
611{
612 for (Argument *arg : args) {
613 if (arg != except && arg->isPresent() && !arg->isCombinable()) {
614 return arg;
615 }
616 }
617 return nullptr;
618}
619
635{
636 // remove this argument from the parents list of the previous secondary arguments
637 for (Argument *arg : m_subArgs) {
638 arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(), this), arg->m_parents.end());
639 }
640 // assign secondary arguments
641 m_subArgs.assign(secondaryArguments);
642 // add this argument to the parents list of the assigned secondary arguments
643 // and set the parser
644 for (Argument *arg : m_subArgs) {
645 if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
646 arg->m_parents.push_back(this);
647 }
648 }
649}
650
659{
660 if (find(m_subArgs.cbegin(), m_subArgs.cend(), arg) != m_subArgs.cend()) {
661 return;
662 }
663 m_subArgs.push_back(arg);
664 if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
665 arg->m_parents.push_back(this);
666 }
667}
668
674{
675 if (isMainArgument()) {
676 return true;
677 }
678 for (const Argument *parent : m_parents) {
679 if (parent->isPresent()) {
680 return true;
681 }
682 }
683 return false;
684}
685
695{
696 return isPresent() ? wouldConflictWithArgument() : nullptr;
697}
698
708{
709 if (isCombinable()) {
710 return nullptr;
711 }
712 for (Argument *parent : m_parents) {
713 for (Argument *sibling : parent->subArguments()) {
714 if (sibling != this && sibling->isPresent() && !sibling->isCombinable()) {
715 return sibling;
716 }
717 }
718 }
719 return nullptr;
720}
721
727{
728 for (Argument *arg : m_subArgs) {
729 if (arg->denotesOperation() && arg->isPresent()) {
730 return arg;
731 }
732 }
733 return nullptr;
734}
735
741{
742 for (Argument *arg : m_subArgs) {
743 arg->resetRecursively();
744 }
745 reset();
746}
747
765 : m_actualArgc(0)
766 , m_executable(nullptr)
767 , m_unknownArgBehavior(UnknownArgumentBehavior::Fail)
768 , m_defaultArg(nullptr)
769 , m_helpArg(*this)
770{
771}
772
783{
784 if (!mainArguments.size()) {
785 m_mainArgs.clear();
786 return;
787 }
788 for (Argument *arg : mainArguments) {
789 arg->m_isMainArg = true;
790 }
791 m_mainArgs.assign(mainArguments);
792 if (m_defaultArg || (*mainArguments.begin())->requiredValueCount()) {
793 return;
794 }
795 bool subArgsRequired = false;
796 for (const Argument *subArg : (*mainArguments.begin())->subArguments()) {
797 if (subArg->isRequired()) {
798 subArgsRequired = true;
799 break;
800 }
801 }
802 if (!subArgsRequired) {
803 m_defaultArg = *mainArguments.begin();
804 }
805}
806
814{
815 argument->m_isMainArg = true;
816 m_mainArgs.push_back(argument);
817}
818
822void ArgumentParser::printHelp(ostream &os) const
823{
825 bool wroteLine = false;
827 os << applicationInfo.name;
829 os << ',' << ' ';
830 }
831 wroteLine = true;
832 }
834 os << "version " << applicationInfo.version;
835 wroteLine = true;
836 }
837 if (wroteLine) {
838 os << '\n' << '\n';
839 }
841
844 wroteLine = true;
845 }
846 if (wroteLine) {
847 os << '\n' << '\n';
848 }
849
850 if (!m_mainArgs.empty()) {
851 bool hasOperations = false, hasTopLevelOptions = false;
852 for (const Argument *const arg : m_mainArgs) {
853 if (arg->denotesOperation()) {
854 hasOperations = true;
855 } else if (strcmp(arg->name(), "help")) {
856 hasTopLevelOptions = true;
857 }
858 if (hasOperations && hasTopLevelOptions) {
859 break;
860 }
861 }
862
863 // check whether operations are available
864 if (hasOperations) {
865 // split top-level operations and other configurations
866 os << "Available operations:";
867 for (const Argument *const arg : m_mainArgs) {
868 if (!arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(), "help")) {
869 continue;
870 }
871 os << '\n';
872 arg->printInfo(os);
873 }
874 if (hasTopLevelOptions) {
875 os << "\nAvailable top-level options:";
876 for (const Argument *const arg : m_mainArgs) {
877 if (arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(), "help")) {
878 continue;
879 }
880 os << '\n';
881 arg->printInfo(os);
882 }
883 }
884 } else {
885 // just show all args if no operations are available
886 os << "Available arguments:";
887 for (const Argument *const arg : m_mainArgs) {
888 if (arg->isDeprecated() || !strcmp(arg->name(), "help")) {
889 continue;
890 }
891 os << '\n';
892 arg->printInfo(os);
893 }
894 }
895 }
896
897 if (!applicationInfo.dependencyVersions.empty()) {
898 os << '\n';
900 os << "Linked against: " << *i;
901 for (++i; i != end; ++i) {
902 os << ',' << ' ' << *i;
903 }
904 os << '\n';
905 }
906
908 os << "\nProject website: " << applicationInfo.url << endl;
909 }
910}
911
928void ArgumentParser::parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior)
929{
930 try {
931 readArgs(argc, argv);
932 if (!argc) {
933 return;
934 }
936 checkConstraints(m_mainArgs);
937 }
939 invokeCallbacks(m_mainArgs);
940 }
941 } catch (const ParseError &failure) {
944 cerr << failure;
945 invokeExit(EXIT_FAILURE);
946 }
947 throw;
948 }
949}
950
964void ArgumentParser::readArgs(int argc, const char *const *argv)
965{
966 CPP_UTILITIES_IF_DEBUG_BUILD(verifyArgs(m_mainArgs);)
967 m_actualArgc = 0;
968
969 // the first argument is the executable name
970 if (!argc) {
971 m_executable = nullptr;
972 return;
973 }
974 m_executable = *argv;
975
976 // check for further arguments
977 if (!--argc) {
978 // no arguments specified -> flag default argument as present if one is assigned
979 if (m_defaultArg) {
980 m_defaultArg->m_occurrences.emplace_back(0);
981 }
982 return;
983 }
984
985 // check for completion mode: if first arg (after executable name) is "--bash-completion-for", bash completion for the following arguments is requested
986 const bool completionMode = !strcmp(*++argv, "--bash-completion-for");
987
988 // determine the index of the current word for completion and the number of arguments to be passed to ArgumentReader
989 unsigned int currentWordIndex = 0, argcForReader;
990 if (completionMode) {
991 // the first argument after "--bash-completion-for" is the index of the current word
992 try {
993 currentWordIndex = (--argc ? stringToNumber<unsigned int, string>(*(++argv)) : 0);
994 if (argc) {
995 ++argv;
996 --argc;
997 }
998 } catch (const ConversionException &) {
999 currentWordIndex = static_cast<unsigned int>(argc - 1);
1000 }
1001 argcForReader = min(static_cast<unsigned int>(argc), currentWordIndex + 1);
1002 } else {
1003 argcForReader = static_cast<unsigned int>(argc);
1004 }
1005
1006 // read specified arguments
1007 ArgumentReader reader(*this, argv, argv + argcForReader, completionMode);
1008 const bool allArgsProcessed(reader.read());
1009 m_noColorArg.apply();
1010
1011 // fail when not all arguments could be processed, except when in completion mode
1012 if (!completionMode && !allArgsProcessed) {
1013 const auto suggestions(findSuggestions(argc, argv, static_cast<unsigned int>(argc - 1), reader));
1014 throw ParseError(argsToString("The specified argument \"", *reader.argv, "\" is unknown.", suggestions));
1015 }
1016
1017 // print Bash completion and prevent the application to continue with the regular execution
1018 if (completionMode) {
1019 printBashCompletion(argc, argv, currentWordIndex, reader);
1020 invokeExit(EXIT_SUCCESS);
1021 }
1022}
1023
1029{
1030 for (Argument *arg : m_mainArgs) {
1031 arg->resetRecursively();
1032 }
1033 m_actualArgc = 0;
1034}
1035
1042{
1043 for (Argument *arg : m_mainArgs) {
1044 if (arg->denotesOperation() && arg->isPresent()) {
1045 return arg;
1046 }
1047 }
1048 return nullptr;
1049}
1050
1055{
1056 for (const Argument *arg : m_mainArgs) {
1057 if (!arg->isCombinable() && arg->isPresent()) {
1058 return true;
1059 }
1060 }
1061 return false;
1062}
1063
1064#ifdef CPP_UTILITIES_DEBUG_BUILD
1079void ArgumentParser::verifyArgs(const ArgumentVector &args)
1080{
1081 vector<const Argument *> verifiedArgs;
1082 verifiedArgs.reserve(args.size());
1083 vector<char> abbreviations;
1084 abbreviations.reserve(abbreviations.size() + args.size());
1085 vector<const char *> names;
1086 names.reserve(names.size() + args.size());
1087 bool hasImplicit = false;
1088 for (const Argument *arg : args) {
1089 assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
1090 verifiedArgs.push_back(arg);
1091 assert(!arg->isImplicit() || !hasImplicit);
1092 hasImplicit |= arg->isImplicit();
1093 assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
1094 abbreviations.push_back(arg->abbreviation());
1095 assert(!arg->name() || find_if(names.cbegin(), names.cend(), [arg](const char *name) { return !strcmp(arg->name(), name); }) == names.cend());
1096 assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0 || (arg->flags() & Argument::Flags::Greedy));
1097 names.emplace_back(arg->name());
1098 }
1099 for (const Argument *arg : args) {
1100 verifyArgs(arg->subArguments());
1101 }
1102}
1103#endif
1104
1112bool compareArgs(const Argument *arg1, const Argument *arg2)
1113{
1114 if (arg1->denotesOperation() && !arg2->denotesOperation()) {
1115 return true;
1116 } else if (!arg1->denotesOperation() && arg2->denotesOperation()) {
1117 return false;
1118 } else {
1119 return strcmp(arg1->name(), arg2->name()) < 0;
1120 }
1121}
1122
1127void insertSiblings(const ArgumentVector &siblings, list<const Argument *> &target)
1128{
1129 bool onlyCombinable = false;
1130 for (const Argument *sibling : siblings) {
1131 if (sibling->isPresent() && !sibling->isCombinable()) {
1132 onlyCombinable = true;
1133 break;
1134 }
1135 }
1136 for (const Argument *sibling : siblings) {
1137 if ((!onlyCombinable || sibling->isCombinable()) && sibling->occurrences() < sibling->maxOccurrences()) {
1138 target.push_back(sibling);
1139 }
1140 }
1141}
1142
1146ArgumentCompletionInfo ArgumentParser::determineCompletionInfo(
1147 int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1148{
1149 ArgumentCompletionInfo completion(reader);
1150
1151 // determine last detected arg
1152 if (completion.lastDetectedArg) {
1153 completion.lastDetectedArgIndex = static_cast<size_t>(reader.lastArgDenotation - argv);
1154 completion.lastDetectedArgPath = completion.lastDetectedArg->path(completion.lastDetectedArg->occurrences() - 1);
1155 }
1156
1157 // determine last arg, omitting trailing empty args
1158 if (argc) {
1159 completion.lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
1160 completion.lastSpecifiedArg = argv + completion.lastSpecifiedArgIndex;
1161 for (; completion.lastSpecifiedArg >= argv && **completion.lastSpecifiedArg == '\0';
1162 --completion.lastSpecifiedArg, --completion.lastSpecifiedArgIndex)
1163 ;
1164 }
1165
1166 // just return main arguments if no args detected
1167 if (!completion.lastDetectedArg || !completion.lastDetectedArg->isPresent()) {
1168 completion.nextArgumentOrValue = true;
1169 insertSiblings(m_mainArgs, completion.relevantArgs);
1170 completion.relevantArgs.sort(compareArgs);
1171 return completion;
1172 }
1173
1174 completion.nextArgumentOrValue = currentWordIndex > completion.lastDetectedArgIndex;
1175 if (!completion.nextArgumentOrValue) {
1176 // since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
1177 completion.relevantArgs.push_back(completion.lastDetectedArg);
1178 completion.relevantArgs.sort(compareArgs);
1179 return completion;
1180 }
1181
1182 // define function to add parameter values of argument as possible completions
1183 const auto addValueCompletionsForArg = [&completion](const Argument *arg) {
1184 if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) {
1185 completion.relevantPreDefinedValues.push_back(arg);
1186 }
1187 if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues) || !arg->preDefinedCompletionValues()) {
1188 completion.completeFiles = completion.completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
1189 completion.completeDirs = completion.completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
1190 }
1191 };
1192
1193 // detect number of specified values
1194 auto currentValueCount = completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size();
1195 // ignore values which are specified after the current word
1196 if (currentValueCount) {
1197 const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - completion.lastDetectedArgIndex;
1198 if (currentValueCount > currentWordIndexRelativeToLastDetectedArg) {
1199 currentValueCount -= currentWordIndexRelativeToLastDetectedArg;
1200 } else {
1201 currentValueCount = 0;
1202 }
1203 }
1204
1205 // add value completions for implicit child if there are no value specified and there are no values required by the
1206 // last detected argument itself
1207 if (!currentValueCount && !completion.lastDetectedArg->requiredValueCount()) {
1208 for (const Argument *child : completion.lastDetectedArg->subArguments()) {
1209 if (child->isImplicit() && child->requiredValueCount()) {
1210 addValueCompletionsForArg(child);
1211 break;
1212 }
1213 }
1214 }
1215
1216 // add value completions for last argument if there are further values required
1217 if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1218 || (currentValueCount < completion.lastDetectedArg->requiredValueCount())) {
1219 addValueCompletionsForArg(completion.lastDetectedArg);
1220 }
1221
1222 if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1223 || completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size()
1224 >= completion.lastDetectedArg->requiredValueCount()) {
1225 // sub arguments of the last arg are possible completions
1226 for (const Argument *subArg : completion.lastDetectedArg->subArguments()) {
1227 if (subArg->occurrences() < subArg->maxOccurrences()) {
1228 completion.relevantArgs.push_back(subArg);
1229 }
1230 }
1231
1232 // siblings of parents are possible completions as well
1233 for (auto parentArgument = completion.lastDetectedArgPath.crbegin(), end = completion.lastDetectedArgPath.crend();; ++parentArgument) {
1234 insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, completion.relevantArgs);
1235 if (parentArgument == end) {
1236 break;
1237 }
1238 }
1239 }
1240
1241 return completion;
1242}
1243
1247string ArgumentParser::findSuggestions(int argc, const char *const *argv, unsigned int cursorPos, const ArgumentReader &reader) const
1248{
1249 // determine completion info
1250 const auto completionInfo(determineCompletionInfo(argc, argv, cursorPos, reader));
1251
1252 // determine the unknown/misspelled argument
1253 const auto *unknownArg(*reader.argv);
1254 auto unknownArgSize(strlen(unknownArg));
1255 // -> refuse suggestions for long args to prevent huge memory allocation for Damerau-Levenshtein algo
1256 if (unknownArgSize > 16) {
1257 return string();
1258 }
1259 // -> remove dashes since argument names internally don't have them
1260 if (unknownArgSize >= 2 && unknownArg[0] == '-' && unknownArg[1] == '-') {
1261 unknownArg += 2;
1262 unknownArgSize -= 2;
1263 }
1264
1265 // find best suggestions limiting the results to 2
1266 multiset<ArgumentSuggestion> bestSuggestions;
1267 // -> consider relevant arguments
1268 for (const Argument *const arg : completionInfo.relevantArgs) {
1269 ArgumentSuggestion(unknownArg, unknownArgSize, arg->name(), !arg->denotesOperation()).addTo(bestSuggestions, 2);
1270 }
1271 // -> consider relevant values
1272 for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1273 if (!arg->preDefinedCompletionValues()) {
1274 continue;
1275 }
1276 for (const char *i = arg->preDefinedCompletionValues(); *i; ++i) {
1277 const char *const wordStart(i);
1278 const char *wordEnd(wordStart + 1);
1279 for (; *wordEnd && *wordEnd != ' '; ++wordEnd)
1280 ;
1281 ArgumentSuggestion(unknownArg, unknownArgSize, wordStart, static_cast<size_t>(wordEnd - wordStart), false).addTo(bestSuggestions, 2);
1282 i = wordEnd;
1283 }
1284 }
1285
1286 // format suggestion
1287 string suggestionStr;
1288 if (const auto suggestionCount = bestSuggestions.size()) {
1289 // allocate memory
1290 size_t requiredSize = 15;
1291 for (const auto &suggestion : bestSuggestions) {
1292 requiredSize += suggestion.suggestionSize + 2;
1293 if (suggestion.hasDashPrefix) {
1294 requiredSize += 2;
1295 }
1296 }
1297 suggestionStr.reserve(requiredSize);
1298
1299 // add each suggestion to end up with something like "Did you mean status (1), pause (3), cat (4), edit (5) or rescan-all (8)?"
1300 suggestionStr += "\nDid you mean ";
1301 size_t i = 0;
1302 for (const auto &suggestion : bestSuggestions) {
1303 if (++i == suggestionCount && suggestionCount != 1) {
1304 suggestionStr += " or ";
1305 } else if (i > 1) {
1306 suggestionStr += ", ";
1307 }
1308 if (suggestion.hasDashPrefix) {
1309 suggestionStr += "--";
1310 }
1311 suggestionStr.append(suggestion.suggestion, suggestion.suggestionSize);
1312 }
1313 suggestionStr += '?';
1314 }
1315 return suggestionStr;
1316}
1317
1323void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1324{
1325 // determine completion info and sort relevant arguments
1326 const auto completionInfo([&] {
1327 auto clutteredCompletionInfo(determineCompletionInfo(argc, argv, currentWordIndex, reader));
1328 clutteredCompletionInfo.relevantArgs.sort(compareArgs);
1329 return clutteredCompletionInfo;
1330 }());
1331
1332 // read the "opening" (started but not finished argument denotation)
1333 const char *opening = nullptr;
1334 string compoundOpening;
1335 size_t openingLen = 0, compoundOpeningStartLen = 0;
1336 unsigned char openingDenotationType = Value;
1337 if (argc && completionInfo.nextArgumentOrValue) {
1338 if (currentWordIndex < static_cast<unsigned int>(argc)) {
1339 opening = argv[currentWordIndex];
1340 // For some reason completions for eg. "set --values disk=1 tag=a" are split so the
1341 // equation sign is an own argument ("set --values disk = 1 tag = a").
1342 // This is not how values are treated by the argument parser. Hence the opening
1343 // must be joined again. In this case only the part after the equation sign needs to be
1344 // provided for completion so compoundOpeningStartLen is set to number of characters to skip.
1345 const size_t minCurrentWordIndex = (completionInfo.lastDetectedArg ? completionInfo.lastDetectedArgIndex : 0);
1346 if (currentWordIndex > minCurrentWordIndex && !strcmp(opening, "=")) {
1347 compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
1348 compoundOpening = argv[currentWordIndex];
1349 compoundOpening += '=';
1350 } else if (currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1], "=")) {
1351 compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
1352 compoundOpening = argv[currentWordIndex];
1353 compoundOpening += '=';
1354 compoundOpening += opening;
1355 }
1356 if (!compoundOpening.empty()) {
1357 opening = compoundOpening.data();
1358 }
1359 } else {
1360 opening = *completionInfo.lastSpecifiedArg;
1361 }
1362 if (*opening == '-') {
1363 ++opening;
1364 ++openingDenotationType;
1365 if (*opening == '-') {
1366 ++opening;
1367 ++openingDenotationType;
1368 }
1369 }
1370 openingLen = strlen(opening);
1371 }
1372
1373 // print "COMPREPLY" bash array
1374 cout << "COMPREPLY=(";
1375 // -> completions for parameter values
1376 bool noWhitespace = false;
1377 for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1378 if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::InvokeCallback && arg->m_callbackFunction) {
1379 arg->m_callbackFunction(arg->isPresent() ? arg->m_occurrences.front() : ArgumentOccurrence(Argument::varValueCount));
1380 }
1381 if (!arg->preDefinedCompletionValues()) {
1382 continue;
1383 }
1384 const bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
1385 if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1386 if (openingDenotationType != Value) {
1387 continue;
1388 }
1389 bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
1390 size_t wordIndex = 0;
1391 for (const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) {
1392 if (wordStart) {
1393 const char *i1 = i, *i2 = opening;
1394 for (; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2)
1395 ;
1396 if ((ok = (i2 == end))) {
1397 cout << '\'';
1398 }
1399 wordStart = false;
1400 wordIndex = 0;
1401 } else if ((wordStart = (*i == ' ') || (*i == '\n'))) {
1402 equationSignAlreadyPresent = false;
1403 if (ok) {
1404 cout << '\'' << ' ';
1405 }
1406 ++i;
1407 continue;
1408 } else if (*i == '=') {
1409 equationSignAlreadyPresent = true;
1410 }
1411 if (!ok) {
1412 ++i;
1413 continue;
1414 }
1415 if (!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
1416 if (*i == '\'') {
1417 cout << "'\"'\"'";
1418 } else {
1419 cout << *i;
1420 }
1421 }
1422 ++i;
1423 ++wordIndex;
1424 switch (*i) {
1425 case ' ':
1426 case '\n':
1427 case '\0':
1428 if (appendEquationSign && !equationSignAlreadyPresent) {
1429 cout << '=';
1430 noWhitespace = true;
1431 equationSignAlreadyPresent = false;
1432 }
1433 if (*i == '\0') {
1434 cout << '\'';
1435 }
1436 }
1437 }
1438 cout << ' ';
1439 } else if (const char *i = arg->preDefinedCompletionValues()) {
1440 bool equationSignAlreadyPresent = false;
1441 cout << '\'';
1442 while (*i) {
1443 if (*i == '\'') {
1444 cout << "'\"'\"'";
1445 } else {
1446 cout << *i;
1447 }
1448 switch (*(++i)) {
1449 case '=':
1450 equationSignAlreadyPresent = true;
1451 break;
1452 case ' ':
1453 case '\n':
1454 case '\0':
1455 if (appendEquationSign && !equationSignAlreadyPresent) {
1456 cout << '=';
1457 equationSignAlreadyPresent = false;
1458 }
1459 if (*i != '\0') {
1460 cout << '\'';
1461 if (*(++i)) {
1462 cout << ' ' << '\'';
1463 }
1464 }
1465 }
1466 }
1467 cout << '\'' << ' ';
1468 }
1469 }
1470 // -> completions for further arguments
1471 for (const Argument *const arg : completionInfo.relevantArgs) {
1472 if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1473 switch (openingDenotationType) {
1474 case Value:
1475 if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1476 continue;
1477 }
1478 break;
1479 case Abbreviation:
1480 break;
1481 case FullName:
1482 if (strncmp(arg->name(), opening, openingLen)) {
1483 continue;
1484 }
1485 }
1486 }
1487
1488 if (opening && openingDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1489 // TODO: add test for this case
1490 cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
1491 } else if (completionInfo.lastDetectedArg && reader.argDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1492 if (reader.argv == reader.end) {
1493 cout << '\'' << *(reader.argv - 1) << '\'' << ' ';
1494 }
1495 } else if (arg->denotesOperation()) {
1496 cout << '\'' << arg->name() << '\'' << ' ';
1497 } else {
1498 cout << '\'' << '-' << '-' << arg->name() << '\'' << ' ';
1499 }
1500 }
1501 // -> completions for files and dirs
1502 // -> if there's already an "opening", determine the dir part and the file part
1503 string actualDir, actualFile;
1504 bool haveFileOrDirCompletions = false;
1505 if (argc && currentWordIndex == completionInfo.lastSpecifiedArgIndex && opening) {
1506 // the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
1507 string unescapedOpening(opening);
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 findAndReplace<string>(unescapedOpening, "\\\\", "\\");
1518 // determine the "directory" part
1519 string dir = directory(unescapedOpening);
1520 if (dir.empty()) {
1521 actualDir = ".";
1522 } else {
1523 if (dir[0] == '\"' || dir[0] == '\'') {
1524 dir.erase(0, 1);
1525 }
1526 if (dir.size() > 1 && (dir[dir.size() - 2] == '\"' || dir[dir.size() - 2] == '\'')) {
1527 dir.erase(dir.size() - 2, 1);
1528 }
1529 actualDir = move(dir);
1530 }
1531 // determine the "file" part
1532 string file = fileName(unescapedOpening);
1533 if (file[0] == '\"' || file[0] == '\'') {
1534 file.erase(0, 1);
1535 }
1536 if (file.size() > 1 && (file[file.size() - 2] == '\"' || file[file.size() - 2] == '\'')) {
1537 file.erase(file.size() - 2, 1);
1538 }
1539 actualFile = move(file);
1540 }
1541
1542 // -> completion for files and dirs
1543#ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
1544 if (completionInfo.completeFiles || completionInfo.completeDirs) {
1545 try {
1546 const auto replace = "'"s, with = "'\"'\"'"s;
1547 const auto useActualDir = argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening;
1548 const auto dirEntries = [&] {
1549 filesystem::directory_iterator i;
1550 if (useActualDir) {
1551 i = filesystem::directory_iterator(actualDir);
1552 findAndReplace(actualDir, replace, with);
1553 } else {
1554 i = filesystem::directory_iterator(".");
1555 }
1556 return i;
1557 }();
1558 for (const auto &dirEntry : dirEntries) {
1559 if (!completionInfo.completeDirs && dirEntry.is_directory()) {
1560 continue;
1561 }
1562 if (!completionInfo.completeFiles && !dirEntry.is_directory()) {
1563 continue;
1564 }
1565 auto dirEntryName = dirEntry.path().filename().string();
1566 auto hasStartingQuote = false;
1567 if (useActualDir) {
1568 if (!startsWith(dirEntryName, actualFile)) {
1569 continue;
1570 }
1571 cout << '\'';
1572 hasStartingQuote = true;
1573 if (actualDir != ".") {
1574 cout << actualDir;
1575 }
1576 }
1577 findAndReplace(dirEntryName, replace, with);
1578 if (!hasStartingQuote) {
1579 cout << '\'';
1580 }
1581 cout << dirEntryName << '\'' << ' ';
1582 haveFileOrDirCompletions = true;
1583 }
1584 } catch (const filesystem::filesystem_error &) {
1585 // ignore filesystem errors; there's no good way to report errors when printing bash completion
1586 }
1587 }
1588#endif
1589 cout << ')';
1590
1591 // ensure file or dir completions are formatted appropriately
1592 if (haveFileOrDirCompletions) {
1593 cout << "; compopt -o filenames";
1594 }
1595
1596 // ensure trailing whitespace is omitted
1597 if (noWhitespace) {
1598 cout << "; compopt -o nospace";
1599 }
1600
1601 cout << endl;
1602}
1603
1609{
1610 for (const Argument *arg : args) {
1611 const auto occurrences = arg->occurrences();
1612 if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1613 throw ParseError(argsToString("The argument \"", arg->name(), "\" mustn't be specified more than ", arg->maxOccurrences(),
1614 (arg->maxOccurrences() == 1 ? " time." : " times.")));
1615 }
1616 if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1617 throw ParseError(argsToString("The argument \"", arg->name(), "\" must be specified at least ", arg->minOccurrences(),
1618 (arg->minOccurrences() == 1 ? " time." : " times.")));
1619 }
1620 Argument *conflictingArgument = nullptr;
1621 if (arg->isMainArgument()) {
1622 if (!arg->isCombinable() && arg->isPresent()) {
1623 conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
1624 }
1625 } else {
1626 conflictingArgument = arg->conflictsWithArgument();
1627 }
1628 if (conflictingArgument) {
1629 throw ParseError(argsToString("The argument \"", conflictingArgument->name(), "\" can not be combined with \"", arg->name(), "\"."));
1630 }
1631 for (size_t i = 0; i != occurrences; ++i) {
1632 if (arg->allRequiredValuesPresent(i)) {
1633 continue;
1634 }
1635 stringstream ss(stringstream::in | stringstream::out);
1636 ss << "Not all parameters for argument \"" << arg->name() << "\" ";
1637 if (i) {
1638 ss << " (" << (i + 1) << " occurrence) ";
1639 }
1640 ss << "provided. You have to provide the following parameters:";
1641 size_t valueNamesPrint = 0;
1642 for (const auto &name : arg->m_valueNames) {
1643 ss << ' ' << name;
1644 ++valueNamesPrint;
1645 }
1646 if (arg->m_requiredValueCount != Argument::varValueCount) {
1647 while (valueNamesPrint < arg->m_requiredValueCount) {
1648 ss << "\nvalue " << (++valueNamesPrint);
1649 }
1650 }
1651 throw ParseError(ss.str());
1652 }
1653
1654 // check constraints of sub arguments recursively
1655 checkConstraints(arg->m_subArgs);
1656 }
1657}
1658
1667{
1668 for (const Argument *arg : args) {
1669 // invoke the callback for each occurrence of the argument
1670 if (arg->m_callbackFunction) {
1671 for (const auto &occurrence : arg->m_occurrences) {
1672 arg->m_callbackFunction(occurrence);
1673 }
1674 }
1675 // invoke the callbacks for sub arguments recursively
1676 invokeCallbacks(arg->m_subArgs);
1677 }
1678}
1679
1683void ArgumentParser::invokeExit(int code)
1684{
1685 if (m_exitFunction) {
1686 m_exitFunction(code);
1687 return;
1688 }
1689 std::exit(code);
1690}
1691
1702 : Argument("help", 'h', "shows this information")
1703{
1704 setCallback([&parser](const ArgumentOccurrence &) {
1706 parser.printHelp(cout);
1707 });
1708}
1709
1745#ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1746 : Argument("no-color", '\0', "disables formatted/colorized output")
1747#else
1748 : Argument("enable-color", '\0', "enables formatted/colorized output")
1749#endif
1750{
1751 setCombinable(true);
1752
1753 // set the environment variable (not directly used and just assigned for printing help)
1754 setEnvironmentVariable("ENABLE_ESCAPE_CODES");
1755
1756 // initialize EscapeCodes::enabled from environment variable
1757 const auto escapeCodesEnabled = isEnvVariableSet(environmentVariable());
1758 if (escapeCodesEnabled.has_value()) {
1759 EscapeCodes::enabled = escapeCodesEnabled.value();
1760 }
1761}
1762
1767{
1768 if (isPresent()) {
1769#ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1770 EscapeCodes::enabled = false;
1771#else
1772 EscapeCodes::enabled = true;
1773#endif
1774 }
1775}
1776
1780void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(const std::vector<Argument *> &argumentPath) const
1781{
1782 throw ParseError(argumentPath.empty()
1783 ? argsToString("Conversion of top-level value \"", valueToConvert, "\" to type \"", targetTypeName, "\" failed: ", errorMessage)
1784 : argsToString("Conversion of value \"", valueToConvert, "\" (for argument --", argumentPath.back()->name(), ") to type \"",
1785 targetTypeName, "\" failed: ", errorMessage));
1786}
1787
1791void ArgumentOccurrence::throwNumberOfValuesNotSufficient(unsigned long valuesToConvert) const
1792{
1793 throw ParseError(path.empty()
1794 ? argsToString("Expected ", valuesToConvert, " top-level values to be present but only ", values.size(), " have been specified.")
1795 : argsToString("Expected ", valuesToConvert, " values for argument --", path.back()->name(), " to be present but only ", values.size(),
1796 " have been specified."));
1797}
1798
1799} // namespace CppUtilities
#define CPP_UTILITIES_IF_DEBUG_BUILD(x)
Wraps debug-only lines conveniently.
Definition: global.h:102
The ArgumentParser class provides a means for handling command line arguments.
void checkConstraints()
Checks whether constraints are violated.
ArgumentParser()
Constructs a new ArgumentParser.
void readArgs(int argc, const char *const *argv)
Parses the specified command line arguments.
const ArgumentVector & mainArguments() const
Returns the main arguments.
bool isUncombinableMainArgPresent() const
Checks whether at least one uncombinable main argument is present.
void setMainArguments(const ArgumentInitializerList &mainArguments)
Sets the main arguments for the parser.
void printHelp(std::ostream &os) const
Prints help text for all assigned arguments.
void addMainArgument(Argument *argument)
Adds the specified argument to the main argument.
void parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior=ParseArgumentBehavior::CheckConstraints|ParseArgumentBehavior::InvokeCallbacks|ParseArgumentBehavior::ExitOnFailure)
Parses the specified command line arguments.
void invokeCallbacks()
Invokes all assigned callbacks.
void resetArgs()
Resets all Argument instances assigned as mainArguments() and sub arguments.
Argument * specifiedOperation() const
Returns the first operation argument specified by the user or nullptr if no operation has been specif...
The ArgumentReader class internally encapsulates the process of reading command line arguments.
const char *const * lastArgDenotation
Points to the element in argv where lastArg was encountered. Unspecified if lastArg is not set.
const char * argDenotation
The currently processed abbreviation denotation (should be substring of one of the args in argv)....
ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode=false)
Initializes the internal reader for the specified parser and arguments.
ArgumentVector & args
The Argument instances to store the results. Sub arguments of args are considered as well.
const char *const * argv
Points to the first argument denotation and will be incremented when a denotation has been processed.
Argument * lastArg
The last Argument instance which could be detected. Set to nullptr in the initial call....
bool completionMode
Whether completion mode is enabled. In this case reading args will be continued even if an denotation...
unsigned char argDenotationType
The type of the currently processed abbreviation denotation. Unspecified if argDenotation is not set.
std::size_t index
An index which is incremented when an argument is encountered (the current index is stored in the occ...
ArgumentReader & reset(const char *const *argv, const char *const *end)
Resets the ArgumentReader to continue reading new argv.
ArgumentParser & parser
The associated ArgumentParser instance.
bool read()
Reads the commands line arguments specified when constructing the object.
const char *const * end
Points to the end of the argv array.
The Argument class is a wrapper for command line argument information.
const char * description() const
Returns the description of the argument.
Argument(const char *name, char abbreviation='\0', const char *description=nullptr, const char *example=nullptr)
Constructs an Argument with the given name, abbreviation and description.
bool isParentPresent() const
Returns whether at least one parent argument is present.
void addSubArgument(Argument *arg)
Adds arg as a secondary argument for this argument.
static constexpr std::size_t varValueCount
Denotes a variable number of values.
const std::vector< Argument * > & path(std::size_t occurrence=0) const
Returns the path of the specified occurrence.
bool isMainArgument() const
Returns an indication whether the argument is used as main argument.
const char * firstValue() const
Returns the first parameter value of the first occurrence of the argument.
const ArgumentVector & subArguments() const
Returns the secondary arguments for this argument.
const char * example() const
Returns the usage example of the argument.
Flags
The Flags enum specifies options for treating the argument in a special way.
const std::vector< const char * > & valueNames() const
Returns the names of the required values.
char abbreviation() const
Returns the abbreviation of the argument.
std::size_t occurrences() const
Returns how often the argument could be detected when parsing.
Argument * specifiedOperation() const
Returns the first operation argument specified by the user or nullptr if no operation has been specif...
const char * name() const
Returns the name of the argument.
bool isCombinable() const
Returns an indication whether the argument is combinable.
bool denotesOperation() const
Returns whether the argument denotes an operation.
void resetRecursively()
Resets this argument and all sub arguments recursively.
Argument::Flags flags() const
Returns Argument::Flags for the argument.
std::size_t requiredValueCount() const
Returns the number of values which are required to be given for this argument.
const char * environmentVariable() const
Returns the environment variable queried when firstValue() is called.
Argument * wouldConflictWithArgument() const
Checks if this argument would conflict with other arguments if it was present.
void reset()
Resets occurrences (indices, values and paths).
~Argument()
Destroys the Argument.
bool isPresent() const
Returns an indication whether the argument could be detected when parsing.
Argument * conflictsWithArgument() const
Checks if this arguments conflicts with other arguments.
void setSubArguments(const ArgumentInitializerList &subArguments)
Sets the secondary arguments for this arguments.
void setCallback(CallbackFunction callback)
Sets a callback function which will be called by the parser if the argument could be found and no par...
bool isRequired() const
Returns an indication whether the argument is mandatory.
void printInfo(std::ostream &os, unsigned char indentation=0) const
Writes the name, the abbreviation and other information about the Argument to the give ostream.
const char * firstValueOr(const char *fallback) const
Returns the first value like Argument::firstValue() but returns fallback instead of nullptr if there'...
The ConversionException class is thrown by the various conversion functions of this library when a co...
HelpArgument(ArgumentParser &parser)
Constructs a new help argument for the specified parser.
The Indentation class allows printing indentation conveniently, eg.
void apply() const
Sets EscapeCodes::enabled according to the presence of the first instantiation of NoColorArgument.
NoColorArgument()
Constructs a new NoColorArgument argument.
The ParseError class is thrown by an ArgumentParser when a parsing error occurs.
Definition: parseerror.h:11
The Wrapper class is internally used print text which might needs to be wrapped preserving the indent...
#define CMD_UTILS_START_CONSOLE
#define CPP_UTILITIES_EXPORT
Marks the symbol to be exported by the c++utilities library.
Encapsulates functions for formatted terminal output using ANSI escape codes.
CPP_UTILITIES_EXPORT bool enabled
Controls whether the functions inside the EscapeCodes namespace actually make use of escape codes.
void setStyle(std::ostream &stream, TextAttribute displayAttribute=TextAttribute::Reset)
Contains all utilities provides by the c++utilities library.
void findAndReplace(StringType1 &str, const StringType2 &find, const StringType3 &replace)
Replaces all occurrences of find with relpace in the specified str.
CPP_UTILITIES_EXPORT TerminalSize determineTerminalSize()
Returns the current size of the terminal.
std::vector< Argument * > ArgumentVector
Argument * firstPresentUncombinableArg(const ArgumentVector &args, const Argument *except)
This function return the first present and uncombinable argument of the given list of arguments.
CPP_UTILITIES_EXPORT std::ostream & operator<<(std::ostream &out, Indentation indentation)
UnknownArgumentBehavior
The UnknownArgumentBehavior enum specifies the behavior of the argument parser when an unknown argume...
bool startsWith(const StringType &str, const StringType &phrase)
Returns whether str starts with phrase.
ParseArgumentBehavior
The ParseArgumentBehavior enum specifies the behavior when parsing arguments.
std::initializer_list< Argument * > ArgumentInitializerList
bool operator==(const AsHexNumber< T > &lhs, const AsHexNumber< T > &rhs)
Provides operator == required by CPPUNIT_ASSERT_EQUAL.
Definition: testutils.h:230
StringType argsToString(Args &&...args)
constexpr T max(T first, T second)
Returns the greatest of the given items.
Definition: math.h:100
ValueCompletionBehavior
The ValueCompletionBehavior enum specifies the items to be considered when generating completion for ...
ArgumentDenotationType
The ArgumentDenotationType enum specifies the type of a given argument denotation.
std::optional< bool > isEnvVariableSet(const char *variableName)
Returns whether the specified env variable is set to a non-zero and non-white-space-only value.
constexpr T min(T first, T second)
Returns the smallest of the given items.
Definition: math.h:88
void insertSiblings(const ArgumentVector &siblings, list< const Argument * > &target)
Inserts the specified siblings in the target list.
bool compareArgs(const Argument *arg1, const Argument *arg2)
Returns whether arg1 should be listed before arg2 when printing completion.
CPP_UTILITIES_EXPORT std::size_t computeDamerauLevenshteinDistance(const char *str1, std::size_t size1, const char *str2, std::size_t size2)
CPP_UTILITIES_EXPORT ApplicationInfo applicationInfo
Stores global application info used by ArgumentParser::printHelp() and AboutDialog.
CPP_UTILITIES_EXPORT std::string fileName(const std::string &path)
Returns the file name and extension of the specified path string.
Definition: path.cpp:17
CPP_UTILITIES_EXPORT std::string directory(const std::string &path)
Returns the directory of the specified path string (including trailing slash).
Definition: path.cpp:25
STL namespace.
Stores information about an application.
std::vector< const char * > dependencyVersions
The ArgumentCompletionInfo struct holds information internally used for shell completion and suggesti...
list< const Argument * > relevantArgs
ArgumentCompletionInfo(const ArgumentReader &reader)
Constructs a new completion info for the specified reader.
vector< Argument * > lastDetectedArgPath
list< const Argument * > relevantPreDefinedValues
The ArgumentOccurrence struct holds argument values for an occurrence of an argument.
std::vector< const char * > values
The parameter values which have been specified after the occurrence of the argument.
std::vector< Argument * > path
The "path" of the occurrence (the parent elements which have been specified before).
The TerminalSize struct describes a terminal size.
unsigned short columns
number of columns
constexpr int i