C++ Utilities 5.12.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 && 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;
224 } else {
225 // determine denotation type
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;
236 if (*argDenotation == '-') {
239 if (*argDenotation == '-') {
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
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;
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);
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) {
402 cerr << Phrases::Warning << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << Phrases::EndFlush;
403 [[fallthrough]];
405 // ignore unknown denotation
406 ++index;
407 ++argv;
408 argDenotation = nullptr;
409 break;
411 return false;
412 }
413 }
414 } // while(argv != end)
415 return true;
416}
417
424ostream &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
454
456
457inline bool notEmpty(const char *str)
458{
459 return str && *str;
460}
461
463
480Argument::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)
494 , m_preDefinedCompletionValues(nullptr)
495{
496}
497
502{
503}
504
512const 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
526const 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
538void Argument::printInfo(ostream &os, unsigned char indentation) const
539{
540 if (isDeprecated()) {
541 return;
542 }
543 Indentation ident(indentation);
544 os << ident;
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 }
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 }
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
610{
611 for (Argument *arg : args) {
612 if (arg != except && arg->isPresent() && !arg->isCombinable()) {
613 return arg;
614 }
615 }
616 return nullptr;
617}
618
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
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
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
694{
695 return isPresent() ? wouldConflictWithArgument() : nullptr;
696}
697
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
726{
727 for (Argument *arg : m_subArgs) {
728 if (arg->denotesOperation() && arg->isPresent()) {
729 return arg;
730 }
731 }
732 return nullptr;
733}
734
740{
741 for (Argument *arg : m_subArgs) {
742 arg->resetRecursively();
743 }
744 reset();
745}
746
764 : m_actualArgc(0)
765 , m_executable(nullptr)
766 , m_unknownArgBehavior(UnknownArgumentBehavior::Fail)
767 , m_defaultArg(nullptr)
768 , m_helpArg(*this)
769{
770}
771
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
813{
814 argument->m_isMainArg = true;
815 m_mainArgs.push_back(argument);
816}
817
821void ArgumentParser::printHelp(ostream &os) const
822{
824 bool wroteLine = false;
826 os << applicationInfo.name;
828 os << ',' << ' ';
829 }
830 wroteLine = true;
831 }
833 os << "version " << applicationInfo.version;
834 wroteLine = true;
835 }
836 if (wroteLine) {
837 os << '\n' << '\n';
838 }
840
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';
899 os << "Linked against: " << *i;
900 for (++i; i != end; ++i) {
901 os << ',' << ' ' << *i;
902 }
903 os << '\n';
904 }
905
907 os << "\nProject website: " << applicationInfo.url << endl;
908 }
909}
910
927void ArgumentParser::parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior)
928{
929 try {
930 readArgs(argc, argv);
931 if (!argc) {
932 return;
933 }
935 checkConstraints(m_mainArgs);
936 }
938 invokeCallbacks(m_mainArgs);
939 }
940 } catch (const ParseError &failure) {
943 cerr << failure;
944 invokeExit(1);
945 }
946 throw;
947 }
948}
949
963void 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
1028{
1029 for (Argument *arg : m_mainArgs) {
1030 arg->resetRecursively();
1031 }
1032 m_actualArgc = 0;
1033}
1034
1041{
1042 for (Argument *arg : m_mainArgs) {
1043 if (arg->denotesOperation() && arg->isPresent()) {
1044 return arg;
1045 }
1046 }
1047 return nullptr;
1048}
1049
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
1078void 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
1111bool 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
1126void 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
1145ArgumentCompletionInfo 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
1246string 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
1322void 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
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
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
1682void ArgumentParser::invokeExit(int code)
1683{
1684 if (m_exitFunction) {
1685 m_exitFunction(code);
1686 return;
1687 }
1688 std::exit(code);
1689}
1690
1701 : Argument("help", 'h', "shows this information")
1702{
1703 setCallback([&parser](const ArgumentOccurrence &) {
1705 parser.printHelp(cout);
1706 });
1707}
1708
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
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
1779void 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
1790void 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
#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.
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.
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:216
CPP_UTILITIES_EXPORT std::string directory(const std::string &path)
Returns the directory of the specified path string (including trailing slash).
Definition: path.cpp:35
StringType argsToString(Args &&...args)
CPP_UTILITIES_EXPORT std::string fileName(const std::string &path)
Returns the file name and extension of the specified path string.
Definition: path.cpp:15
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.
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