C++ Utilities 5.30.0
Useful C++ classes and routines such as argument parser, IO and conversion utilities
Loading...
Searching...
No Matches
argumentparser.cpp
Go to the documentation of this file.
1#include "./argumentparser.h"
4
8#include "../io/path.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
38enum ArgumentDenotationType : unsigned char {
39 Value = 0,
42};
43
49
52 vector<Argument *> lastDetectedArgPath;
53 list<const Argument *> relevantArgs;
54 list<const Argument *> relevantPreDefinedValues;
55 const char *const *lastSpecifiedArg = nullptr;
56 unsigned int lastSpecifiedArgIndex = 0;
57 bool nextArgumentOrValue = false;
58 bool completeFiles = false, completeDirs = false;
59};
60
69
71struct ArgumentSuggestion {
72 ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool hasDashPrefix);
73 ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, size_t suggestionSize, bool hasDashPrefix);
74 bool operator<(const ArgumentSuggestion &other) const;
75 bool operator==(const ArgumentSuggestion &other) const;
76 void addTo(multiset<ArgumentSuggestion> &suggestions, size_t limit) const;
77
78 const char *const suggestion;
79 const size_t suggestionSize;
80 const size_t editingDistance;
81 const bool hasDashPrefix;
82};
83
84ArgumentSuggestion::ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, size_t suggestionSize, bool isOperation)
85 : suggestion(suggestion)
86 , suggestionSize(suggestionSize)
87 , editingDistance(computeDamerauLevenshteinDistance(unknownArg, unknownArgSize, suggestion, suggestionSize))
88 , hasDashPrefix(isOperation)
89{
90}
91
92ArgumentSuggestion::ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool isOperation)
93 : ArgumentSuggestion(unknownArg, unknownArgSize, suggestion, strlen(suggestion), isOperation)
94{
95}
96
97bool ArgumentSuggestion::operator<(const ArgumentSuggestion &other) const
98{
99 return editingDistance < other.editingDistance;
100}
101
102void ArgumentSuggestion::addTo(multiset<ArgumentSuggestion> &suggestions, size_t limit) const
103{
104 if (suggestions.size() >= limit && !(*this < *--suggestions.end())) {
105 return;
106 }
107 suggestions.emplace(*this);
108 while (suggestions.size() > limit) {
109 suggestions.erase(--suggestions.end());
110 }
111}
113
122
126ArgumentReader::ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode)
127 : parser(parser)
128 , args(parser.m_mainArgs)
129 , index(0)
130 , argv(argv)
131 , end(end)
132 , lastArg(nullptr)
133 , argDenotation(nullptr)
135{
136}
137
141ArgumentReader &ArgumentReader::reset(const char *const *argv, const char *const *end)
142{
143 this->argv = argv;
144 this->end = end;
145 index = 0;
146 lastArg = nullptr;
147 argDenotation = nullptr;
148 return *this;
149}
150
156{
157 return read(args);
158}
159
163bool Argument::matchesDenotation(const char *denotation, size_t denotationLength) const
164{
165 return m_name && !strncmp(m_name, denotation, denotationLength) && *(m_name + denotationLength) == '\0';
166}
167
176{
177 // method is called recursively for sub args to the last argument (which is nullptr in the initial call) is the current parent argument
178 Argument *const parentArg = lastArg;
179 // determine the current path
180 const vector<Argument *> &parentPath = parentArg ? parentArg->path(parentArg->occurrences() - 1) : vector<Argument *>();
181
182 Argument *lastArgInLevel = nullptr;
183 vector<const char *> *values = nullptr;
184
185 // iterate through all argument denotations; loop might exit earlier when a denotation is unknown
186 while (argv != end) {
187 // check whether there are still values to read
188 if (values && ((lastArgInLevel->requiredValueCount() != Argument::varValueCount) || (lastArgInLevel->flags() & Argument::Flags::Greedy))
189 && values->size() < lastArgInLevel->requiredValueCount()) {
190 // read arg as value and continue with next arg
191 values->emplace_back(argDenotation ? argDenotation : *argv);
192 ++index;
193 ++argv;
194 argDenotation = nullptr;
195 continue;
196 }
197
198 // determine how denotation must be processed
199 bool abbreviationFound = false;
200 if (argDenotation) {
201 // continue reading children for abbreviation denotation already detected
202 abbreviationFound = false;
204 } else {
205 // determine denotation type
207 if (!*argDenotation && (!lastArgInLevel || values->size() >= lastArgInLevel->requiredValueCount())) {
208 // skip empty arguments
209 ++index;
210 ++argv;
211 argDenotation = nullptr;
212 continue;
213 }
214 abbreviationFound = false;
216 if (*argDenotation == '-') {
219 if (*argDenotation == '-') {
222 }
223 }
224 }
225
226 // try to find matching Argument instance
227 Argument *matchingArg = nullptr;
228 if (argDenotationType != Value) {
229 // determine actual denotation length (everything before equation sign)
230 const char *const equationPos = strchr(argDenotation, '=');
231 const auto argDenotationLength = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation);
232
233 // loop through each "part" of the denotation
234 // names are read at once, but for abbreviations each character is considered individually
235 for (; argDenotationLength; matchingArg = nullptr) {
236 // search for arguments by abbreviation or name depending on the previously determined denotation type
238 for (Argument *const arg : args) {
239 if (arg->abbreviation() && arg->abbreviation() == *argDenotation) {
240 matchingArg = arg;
241 abbreviationFound = true;
242 break;
243 }
244 }
245 } else {
246 for (Argument *const arg : args) {
247 if (arg->matchesDenotation(argDenotation, argDenotationLength)) {
248 matchingArg = arg;
249 break;
250 }
251 }
252 }
253 if (!matchingArg) {
254 break;
255 }
256
257 // an argument matched the specified denotation so add an occurrence
258 matchingArg->m_occurrences.emplace_back(index, parentPath, parentArg);
259
260 // prepare reading parameter values
261 values = &matchingArg->m_occurrences.back().values;
262
263 // read value after equation sign
264 if ((argDenotationType != Abbreviation && equationPos) || (++argDenotation == equationPos)) {
265 values->push_back(equationPos + 1);
266 argDenotation = nullptr;
267 }
268
269 // read sub arguments, distinguish whether further abbreviations follow
270 ++index;
271 ++parser.m_actualArgc;
272 lastArg = lastArgInLevel = matchingArg;
275 // no further abbreviations follow -> read sub args for next argv
276 ++argv;
277 argDenotation = nullptr;
278 read(lastArg->m_subArgs);
279 argDenotation = nullptr;
280 break;
281 } else {
282 // further abbreviations follow -> remember current arg value
283 const char *const *const currentArgValue = argv;
284 // don't increment argv, keep processing outstanding chars of argDenotation
285 read(lastArg->m_subArgs);
286 // stop further processing if the denotation has been consumed or even the next value has already been loaded
287 if (!argDenotation || currentArgValue != argv) {
288 argDenotation = nullptr;
289 break;
290 }
291 }
292 }
293
294 // continue with next arg if we've got a match already
295 if (matchingArg) {
296 continue;
297 }
298
299 // unknown argument might be a sibling of the parent element
300 for (auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend();; ++parentArgument) {
301 for (Argument *const sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() : parser.m_mainArgs)) {
302 if (sibling->occurrences() < sibling->maxOccurrences()) {
303 // check whether the denoted abbreviation matches the sibling's abbreviatiopn
304 if (argDenotationType == Abbreviation && (sibling->abbreviation() && sibling->abbreviation() == *argDenotation)) {
305 return false;
306 }
307 // check whether the denoted name matches the sibling's name
308 if (sibling->matchesDenotation(argDenotation, argDenotationLength)) {
309 return false;
310 }
311 }
312 }
313 if (parentArgument == pathEnd) {
314 break;
315 }
316 }
317 }
318
319 // unknown argument might just be a parameter value of the last argument
320 if (lastArgInLevel && values->size() < lastArgInLevel->requiredValueCount()) {
321 values->emplace_back(abbreviationFound ? argDenotation : *argv);
322 ++index;
323 ++argv;
324 argDenotation = nullptr;
325 continue;
326 }
327
328 // first value might denote "operation"
329 for (Argument *const arg : args) {
330 if (arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
331 (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
333 ++index;
334 ++argv;
335 break;
336 }
337 }
338
339 // use the first default argument which is not already present if there is still no match
340 if (!matchingArg && (!completionMode || (argv + 1 != end))) {
341 const bool uncombinableMainArgPresent = parentArg ? false : parser.isUncombinableMainArgPresent();
342 for (Argument *const arg : args) {
343 if (arg->isImplicit() && !arg->isPresent() && !arg->wouldConflictWithArgument()
344 && (!uncombinableMainArgPresent || !arg->isMainArgument())) {
345 (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
346 break;
347 }
348 }
349 }
350
351 if (matchingArg) {
352 // an argument matched the specified denotation
353 if (lastArgInLevel == matchingArg) {
354 break; // break required? -> TODO: add test for this condition
355 }
356
357 // prepare reading parameter values
358 values = &matchingArg->m_occurrences.back().values;
359
360 // read sub arguments
361 ++parser.m_actualArgc;
362 lastArg = lastArgInLevel = matchingArg;
363 argDenotation = nullptr;
364 if ((values->size() < matchingArg->requiredValueCount()) && (matchingArg->flags() & Argument::Flags::Greedy)) {
365 continue;
366 }
367 read(lastArg->m_subArgs);
368 argDenotation = nullptr;
369 continue;
370 }
371
372 // argument denotation is unknown -> handle error
373 if (parentArg) {
374 // continue with parent level
375 return false;
376 }
377 if (completionMode) {
378 // ignore unknown denotation
379 ++index;
380 ++argv;
381 argDenotation = nullptr;
382 } else {
383 switch (parser.m_unknownArgBehavior) {
385 cerr << Phrases::Warning << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << Phrases::EndFlush;
386 [[fallthrough]];
388 // ignore unknown denotation
389 ++index;
390 ++argv;
391 argDenotation = nullptr;
392 break;
394 return false;
395 }
396 }
397 } // while(argv != end)
398 return true;
399}
400
406
407ostream &operator<<(ostream &os, const Wrapper &wrapper)
408{
409 // determine max. number of columns
410 static const TerminalSize termSize(determineTerminalSize());
411 const auto maxColumns = termSize.columns ? termSize.columns : numeric_limits<unsigned short>::max();
412
413 // print wrapped string considering indentation
414 unsigned short currentCol = wrapper.m_indentation.level;
415 for (const char *currentChar = wrapper.m_str; *currentChar; ++currentChar) {
416 const bool wrappingRequired = currentCol >= maxColumns;
417 if (wrappingRequired || *currentChar == '\n') {
418 // insert newline (TODO: wrap only at end of a word)
419 os << '\n';
420 // print indentation (if enough space)
421 if (wrapper.m_indentation.level < maxColumns) {
422 os << wrapper.m_indentation;
423 currentCol = wrapper.m_indentation.level;
424 } else {
425 currentCol = 0;
426 }
427 }
428 if (*currentChar != '\n' && (!wrappingRequired || *currentChar != ' ')) {
429 os << *currentChar;
430 ++currentCol;
431 }
432 }
433 return os;
434}
435
437
439
440inline bool notEmpty(const char *str)
441{
442 return str && *str;
443}
444
446
456
463Argument::Argument(const char *name, char abbreviation, const char *description, const char *example)
464 : m_name(name)
465 , m_abbreviation(abbreviation)
466 , m_environmentVar(nullptr)
467 , m_description(description)
468 , m_example(example)
469 , m_minOccurrences(0)
470 , m_maxOccurrences(1)
471 , m_requiredValueCount(0)
472 , m_flags(Flags::None)
473 , m_deprecatedBy(nullptr)
474 , m_isMainArg(false)
477 , m_preDefinedCompletionValues(nullptr)
478{
479}
480
487
495const char *Argument::firstValue() const
496{
497 if (!m_occurrences.empty() && !m_occurrences.front().values.empty()) {
498 return m_occurrences.front().values.front();
499 } else if (m_environmentVar) {
500 return getenv(m_environmentVar);
501 } else {
502 return nullptr;
503 }
504}
505
509const char *Argument::firstValueOr(const char *fallback) const
510{
511 if (const auto *const v = firstValue()) {
512 return v;
513 } else {
514 return fallback;
515 }
516}
517
521void Argument::printInfo(ostream &os, unsigned char indentation) const
522{
523 if (isDeprecated()) {
524 return;
525 }
526 Indentation ident(indentation);
527 os << ident;
529 if (notEmpty(name())) {
530 if (!denotesOperation()) {
531 os << '-' << '-';
532 }
533 os << name();
534 }
535 if (notEmpty(name()) && abbreviation()) {
536 os << ',' << ' ';
537 }
538 if (abbreviation()) {
539 os << '-' << abbreviation();
540 }
542 if (requiredValueCount()) {
543 unsigned int valueNamesPrint = 0;
544 for (auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
545 os << ' ' << '[' << *i << ']';
546 ++valueNamesPrint;
547 }
549 os << " ...";
550 } else {
551 for (; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
552 os << " [value " << (valueNamesPrint + 1) << ']';
553 }
554 }
555 }
556 ident.level += 2;
557 if (notEmpty(description())) {
558 os << '\n' << ident << Wrapper(description(), ident);
559 }
560 if (isRequired()) {
561 os << '\n' << ident << "particularities: mandatory";
562 if (!isMainArgument()) {
563 os << " if parent argument is present";
564 }
565 }
566 if (environmentVariable()) {
567 os << '\n' << ident << "default environment variable: " << Wrapper(environmentVariable(), ident + 30);
568 }
569 os << '\n';
570 bool hasSubArgs = false;
571 for (const auto *const arg : subArguments()) {
572 if (arg->isDeprecated()) {
573 continue;
574 }
575 hasSubArgs = true;
576 arg->printInfo(os, ident.level);
577 }
578 if (notEmpty(example())) {
579 if (ident.level == 2 && hasSubArgs) {
580 os << '\n';
581 }
582 os << ident << "example: " << Wrapper(example(), ident + 9);
583 os << '\n';
584 }
585}
586
593{
594 for (Argument *arg : args) {
595 if (arg != except && arg->isPresent() && !arg->isCombinable()) {
596 return arg;
597 }
598 }
599 return nullptr;
600}
601
618{
619 // remove this argument from the parents list of the previous secondary arguments
620 for (Argument *const arg : m_subArgs) {
621 arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(), this), arg->m_parents.end());
622 }
623 // clear currently assigned args before adding new ones
624 m_subArgs.clear();
626}
627
644{
645 // append secondary arguments
646 const auto requiredCap = m_subArgs.size() + subArguments.size();
647 if (requiredCap < m_subArgs.capacity()) {
648 m_subArgs.reserve(requiredCap); // does insert this for us?
649 }
650 m_subArgs.insert(m_subArgs.end(), subArguments.begin(), subArguments.end());
651 // add this argument to the parents list of the assigned secondary arguments and set the parser
652 for (Argument *const arg : subArguments) {
653 if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
654 arg->m_parents.push_back(this);
655 }
656 }
657}
658
667{
668 if (find(m_subArgs.cbegin(), m_subArgs.cend(), arg) != m_subArgs.cend()) {
669 return;
670 }
671 m_subArgs.push_back(arg);
672 if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
673 arg->m_parents.push_back(this);
674 }
675}
676
682{
683 if (isMainArgument()) {
684 return true;
685 }
686 for (const Argument *parent : m_parents) {
687 if (parent->isPresent()) {
688 return true;
689 }
690 }
691 return false;
692}
693
706
716{
717 if (isCombinable()) {
718 return nullptr;
719 }
720 for (Argument *parent : m_parents) {
721 for (Argument *sibling : parent->subArguments()) {
722 if (sibling != this && sibling->isPresent() && !sibling->isCombinable()) {
723 return sibling;
724 }
725 }
726 }
727 return nullptr;
728}
729
735{
736 for (Argument *arg : m_subArgs) {
737 if (arg->denotesOperation() && arg->isPresent()) {
738 return arg;
739 }
740 }
741 return nullptr;
742}
743
749{
750 for (Argument *arg : m_subArgs) {
751 arg->resetRecursively();
752 }
753 reset();
754}
755
768
773 : m_actualArgc(0)
774 , m_executable(nullptr)
775 , m_unknownArgBehavior(UnknownArgumentBehavior::Fail)
776 , m_defaultArg(nullptr)
777 , m_helpArg(*this)
778{
779}
780
791{
792 if (!mainArguments.size()) {
793 m_mainArgs.clear();
794 return;
795 }
796 for (Argument *arg : mainArguments) {
797 arg->m_isMainArg = true;
798 }
799 m_mainArgs.assign(mainArguments);
800 if (m_defaultArg || (*mainArguments.begin())->requiredValueCount()) {
801 return;
802 }
803 bool subArgsRequired = false;
804 for (const Argument *subArg : (*mainArguments.begin())->subArguments()) {
805 if (subArg->isRequired()) {
806 subArgsRequired = true;
807 break;
808 }
809 }
810 if (!subArgsRequired) {
811 m_defaultArg = *mainArguments.begin();
812 }
813}
814
822{
823 argument->m_isMainArg = true;
824 m_mainArgs.push_back(argument);
825}
826
830void ArgumentParser::printHelp(ostream &os) const
831{
833 bool wroteLine = false;
834 if (applicationInfo.name && *applicationInfo.name) {
835 os << applicationInfo.name;
836 if (applicationInfo.version && *applicationInfo.version) {
837 os << ',' << ' ';
838 }
839 wroteLine = true;
840 }
841 if (applicationInfo.version && *applicationInfo.version) {
842 os << "version " << applicationInfo.version;
843 wroteLine = true;
844 }
845 if (wroteLine) {
846 os << '\n' << '\n';
847 }
849
850 if (applicationInfo.description && *applicationInfo.description) {
851 os << applicationInfo.description;
852 wroteLine = true;
853 }
854 if (wroteLine) {
855 os << '\n' << '\n';
856 }
857
858 if (!m_mainArgs.empty()) {
859 bool hasOperations = false, hasTopLevelOptions = false;
860 for (const Argument *const arg : m_mainArgs) {
861 if (arg->denotesOperation()) {
862 hasOperations = true;
863 } else if (strcmp(arg->name(), "help")) {
864 hasTopLevelOptions = true;
865 }
866 if (hasOperations && hasTopLevelOptions) {
867 break;
868 }
869 }
870
871 // check whether operations are available
872 if (hasOperations) {
873 // split top-level operations and other configurations
874 os << "Available operations:";
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 if (hasTopLevelOptions) {
883 os << "\nAvailable top-level options:";
884 for (const Argument *const arg : m_mainArgs) {
885 if (arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(), "help")) {
886 continue;
887 }
888 os << '\n';
889 arg->printInfo(os);
890 }
891 }
892 } else {
893 // just show all args if no operations are available
894 os << "Available arguments:";
895 for (const Argument *const arg : m_mainArgs) {
896 if (arg->isDeprecated() || !strcmp(arg->name(), "help")) {
897 continue;
898 }
899 os << '\n';
900 arg->printInfo(os);
901 }
902 }
903 }
904
905 if (!applicationInfo.dependencyVersions.empty()) {
906 os << '\n';
907 auto i = applicationInfo.dependencyVersions.begin(), end = applicationInfo.dependencyVersions.end();
908 os << "Linked against: " << *i;
909 for (++i; i != end; ++i) {
910 os << ',' << ' ' << *i;
911 }
912 os << '\n';
913 }
914
915 if (applicationInfo.url && *applicationInfo.url) {
916 os << "\nProject website: " << applicationInfo.url << endl;
917 }
918}
919
936void ArgumentParser::parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior)
937{
938 try {
939 readArgs(argc, argv);
940 if (!argc) {
941 return;
942 }
944 checkConstraints(m_mainArgs);
945 }
947 invokeCallbacks(m_mainArgs);
948 }
949 } catch (const ParseError &failure) {
952 cerr << failure;
953 invokeExit(EXIT_FAILURE);
954 }
955 throw;
956 }
957}
958
972void ArgumentParser::readArgs(int argc, const char *const *argv)
973{
974 CPP_UTILITIES_IF_DEBUG_BUILD(verifyArgs(m_mainArgs);)
975 m_actualArgc = 0;
976
977 // the first argument is the executable name
978 if (!argc) {
979 m_executable = nullptr;
980 return;
981 }
982 m_executable = *argv;
983
984 // check for further arguments
985 if (!--argc) {
986 // no arguments specified -> flag default argument as present if one is assigned
988 return;
989 }
990
991 // check for completion mode: if first arg (after executable name) is "--bash-completion-for", bash completion for the following arguments is requested
992 const bool completionMode = !strcmp(*++argv, "--bash-completion-for");
993
994 // determine the index of the current word for completion and the number of arguments to be passed to ArgumentReader
995 unsigned int currentWordIndex = 0, argcForReader;
996 if (completionMode) {
997 // the first argument after "--bash-completion-for" is the index of the current word
998 try {
999 currentWordIndex = (--argc ? stringToNumber<unsigned int, string>(*(++argv)) : 0);
1000 if (argc) {
1001 ++argv;
1002 --argc;
1003 }
1004 } catch (const ConversionException &) {
1005 currentWordIndex = static_cast<unsigned int>(argc - 1);
1006 }
1007 argcForReader = min(static_cast<unsigned int>(argc), currentWordIndex + 1);
1008 } else {
1009 argcForReader = static_cast<unsigned int>(argc);
1010 }
1011
1012 // read specified arguments
1013 ArgumentReader reader(*this, argv, argv + argcForReader, completionMode);
1014 const bool allArgsProcessed(reader.read());
1015 m_noColorArg.apply();
1016
1017 // fail when not all arguments could be processed, except when in completion mode
1018 if (!completionMode && !allArgsProcessed) {
1019 const auto suggestions(findSuggestions(argc, argv, static_cast<unsigned int>(argc - 1), reader));
1020 throw ParseError(argsToString("The specified argument \"", *reader.argv, "\" is unknown.", suggestions));
1021 }
1022
1023 // print Bash completion and prevent the application to continue with the regular execution
1024 if (completionMode) {
1025 printBashCompletion(argc, argv, currentWordIndex, reader);
1026 invokeExit(EXIT_SUCCESS);
1027 }
1028}
1029
1035{
1036 for (Argument *arg : m_mainArgs) {
1037 arg->resetRecursively();
1038 }
1039 m_actualArgc = 0;
1040}
1041
1053{
1054 if (m_defaultArg && m_defaultArg->m_occurrences.empty()) {
1055 m_defaultArg->m_occurrences.emplace_back(0);
1056 }
1057}
1058
1067{
1068 if (!m_defaultArg || m_defaultArg->isPresent() || !m_defaultArg->denotesOperation()) {
1069 return;
1070 }
1071 for (auto *const arg : m_mainArgs) {
1072 if (arg->isPresent() && (arg->denotesOperation() || arg == &m_helpArg)) {
1073 return;
1074 }
1075 }
1077}
1078
1085{
1086 for (Argument *arg : m_mainArgs) {
1087 if (arg->denotesOperation() && arg->isPresent()) {
1088 return arg;
1089 }
1090 }
1091 return nullptr;
1092}
1093
1098{
1099 for (const Argument *arg : m_mainArgs) {
1100 if (!arg->isCombinable() && arg->isPresent()) {
1101 return true;
1102 }
1103 }
1104 return false;
1105}
1106
1107#ifdef CPP_UTILITIES_DEBUG_BUILD
1122void ArgumentParser::verifyArgs(const ArgumentVector &args)
1123{
1124 vector<const Argument *> verifiedArgs;
1125 verifiedArgs.reserve(args.size());
1126 vector<char> abbreviations;
1127 abbreviations.reserve(abbreviations.size() + args.size());
1128 vector<const char *> names;
1129 names.reserve(names.size() + args.size());
1130 bool hasImplicit = false;
1131 for (const Argument *arg : args) {
1132 assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
1133 verifiedArgs.push_back(arg);
1134 assert(!arg->isImplicit() || !hasImplicit);
1135 hasImplicit |= arg->isImplicit();
1136 assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
1137 abbreviations.push_back(arg->abbreviation());
1138 assert(!arg->name() || find_if(names.cbegin(), names.cend(), [arg](const char *name) { return !strcmp(arg->name(), name); }) == names.cend());
1139 assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0 || (arg->flags() & Argument::Flags::Greedy));
1140 names.emplace_back(arg->name());
1141 }
1142 for (const Argument *arg : args) {
1143 verifyArgs(arg->subArguments());
1144 }
1145}
1146#endif
1147
1155bool compareArgs(const Argument *arg1, const Argument *arg2)
1156{
1157 if (arg1->denotesOperation() && !arg2->denotesOperation()) {
1158 return true;
1159 } else if (!arg1->denotesOperation() && arg2->denotesOperation()) {
1160 return false;
1161 } else {
1162 return strcmp(arg1->name(), arg2->name()) < 0;
1163 }
1164}
1165
1170void insertSiblings(const ArgumentVector &siblings, list<const Argument *> &target)
1171{
1172 bool onlyCombinable = false;
1173 for (const Argument *sibling : siblings) {
1174 if (sibling->isPresent() && !sibling->isCombinable()) {
1175 onlyCombinable = true;
1176 break;
1177 }
1178 }
1179 for (const Argument *sibling : siblings) {
1180 if ((!onlyCombinable || sibling->isCombinable()) && sibling->occurrences() < sibling->maxOccurrences()) {
1181 target.push_back(sibling);
1182 }
1183 }
1184}
1185
1189ArgumentCompletionInfo ArgumentParser::determineCompletionInfo(
1190 int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1191{
1192 ArgumentCompletionInfo completion(reader);
1193
1194 // determine last detected arg
1195 if (completion.lastDetectedArg) {
1196 completion.lastDetectedArgIndex = static_cast<size_t>(reader.lastArgDenotation - argv);
1197 completion.lastDetectedArgPath = completion.lastDetectedArg->path(completion.lastDetectedArg->occurrences() - 1);
1198 }
1199
1200 // determine last arg, omitting trailing empty args
1201 if (argc) {
1202 completion.lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
1203 completion.lastSpecifiedArg = argv + completion.lastSpecifiedArgIndex;
1204 for (; completion.lastSpecifiedArg >= argv && **completion.lastSpecifiedArg == '\0';
1205 --completion.lastSpecifiedArg, --completion.lastSpecifiedArgIndex)
1206 ;
1207 }
1208
1209 // just return main arguments if no args detected
1210 if (!completion.lastDetectedArg || !completion.lastDetectedArg->isPresent()) {
1211 completion.nextArgumentOrValue = true;
1212 insertSiblings(m_mainArgs, completion.relevantArgs);
1213 completion.relevantArgs.sort(compareArgs);
1214 return completion;
1215 }
1216
1217 completion.nextArgumentOrValue = currentWordIndex > completion.lastDetectedArgIndex;
1218 if (!completion.nextArgumentOrValue) {
1219 // since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
1220 completion.relevantArgs.push_back(completion.lastDetectedArg);
1221 completion.relevantArgs.sort(compareArgs);
1222 return completion;
1223 }
1224
1225 // define function to add parameter values of argument as possible completions
1226 const auto addValueCompletionsForArg = [&completion](const Argument *arg) {
1227 if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) {
1228 completion.relevantPreDefinedValues.push_back(arg);
1229 }
1230 if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues) || !arg->preDefinedCompletionValues()) {
1231 completion.completeFiles = completion.completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
1232 completion.completeDirs = completion.completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
1233 }
1234 };
1235
1236 // detect number of specified values
1237 auto currentValueCount = completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size();
1238 // ignore values which are specified after the current word
1239 if (currentValueCount) {
1240 const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - completion.lastDetectedArgIndex;
1241 if (currentValueCount > currentWordIndexRelativeToLastDetectedArg) {
1242 currentValueCount -= currentWordIndexRelativeToLastDetectedArg;
1243 } else {
1244 currentValueCount = 0;
1245 }
1246 }
1247
1248 // add value completions for implicit child if there are no value specified and there are no values required by the
1249 // last detected argument itself
1250 if (!currentValueCount && !completion.lastDetectedArg->requiredValueCount()) {
1251 for (const Argument *child : completion.lastDetectedArg->subArguments()) {
1252 if (child->isImplicit() && child->requiredValueCount()) {
1253 addValueCompletionsForArg(child);
1254 break;
1255 }
1256 }
1257 }
1258
1259 // add value completions for last argument if there are further values required
1260 if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1261 || (currentValueCount < completion.lastDetectedArg->requiredValueCount())) {
1262 addValueCompletionsForArg(completion.lastDetectedArg);
1263 }
1264
1265 if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1266 || completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size()
1267 >= completion.lastDetectedArg->requiredValueCount()) {
1268 // sub arguments of the last arg are possible completions
1269 for (const Argument *subArg : completion.lastDetectedArg->subArguments()) {
1270 if (subArg->occurrences() < subArg->maxOccurrences()) {
1271 completion.relevantArgs.push_back(subArg);
1272 }
1273 }
1274
1275 // siblings of parents are possible completions as well
1276 for (auto parentArgument = completion.lastDetectedArgPath.crbegin(), end = completion.lastDetectedArgPath.crend();; ++parentArgument) {
1277 insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, completion.relevantArgs);
1278 if (parentArgument == end) {
1279 break;
1280 }
1281 }
1282 }
1283
1284 return completion;
1285}
1286
1290string ArgumentParser::findSuggestions(int argc, const char *const *argv, unsigned int cursorPos, const ArgumentReader &reader) const
1291{
1292 // determine completion info
1293 const auto completionInfo(determineCompletionInfo(argc, argv, cursorPos, reader));
1294
1295 // determine the unknown/misspelled argument
1296 const auto *unknownArg(*reader.argv);
1297 auto unknownArgSize(strlen(unknownArg));
1298 // -> refuse suggestions for long args to prevent huge memory allocation for Damerau-Levenshtein algo
1299 if (unknownArgSize > 16) {
1300 return string();
1301 }
1302 // -> remove dashes since argument names internally don't have them
1303 if (unknownArgSize >= 2 && unknownArg[0] == '-' && unknownArg[1] == '-') {
1304 unknownArg += 2;
1305 unknownArgSize -= 2;
1306 }
1307
1308 // find best suggestions limiting the results to 2
1309 multiset<ArgumentSuggestion> bestSuggestions;
1310 // -> consider relevant arguments
1311 for (const Argument *const arg : completionInfo.relevantArgs) {
1312 ArgumentSuggestion(unknownArg, unknownArgSize, arg->name(), !arg->denotesOperation()).addTo(bestSuggestions, 2);
1313 }
1314 // -> consider relevant values
1315 for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1316 if (!arg->preDefinedCompletionValues()) {
1317 continue;
1318 }
1319 for (const char *i = arg->preDefinedCompletionValues(); *i; ++i) {
1320 const char *const wordStart(i);
1321 const char *wordEnd(wordStart + 1);
1322 for (; *wordEnd && *wordEnd != ' '; ++wordEnd)
1323 ;
1324 ArgumentSuggestion(unknownArg, unknownArgSize, wordStart, static_cast<size_t>(wordEnd - wordStart), false).addTo(bestSuggestions, 2);
1325 i = wordEnd;
1326 }
1327 }
1328
1329 // format suggestion
1330 string suggestionStr;
1331 if (const auto suggestionCount = bestSuggestions.size()) {
1332 // allocate memory
1333 size_t requiredSize = 15;
1334 for (const auto &suggestion : bestSuggestions) {
1335 requiredSize += suggestion.suggestionSize + 2;
1336 if (suggestion.hasDashPrefix) {
1337 requiredSize += 2;
1338 }
1339 }
1340 suggestionStr.reserve(requiredSize);
1341
1342 // add each suggestion to end up with something like "Did you mean status (1), pause (3), cat (4), edit (5) or rescan-all (8)?"
1343 suggestionStr += "\nDid you mean ";
1344 size_t i = 0;
1345 for (const auto &suggestion : bestSuggestions) {
1346 if (++i == suggestionCount && suggestionCount != 1) {
1347 suggestionStr += " or ";
1348 } else if (i > 1) {
1349 suggestionStr += ", ";
1350 }
1351 if (suggestion.hasDashPrefix) {
1352 suggestionStr += "--";
1353 }
1354 suggestionStr.append(suggestion.suggestion, suggestion.suggestionSize);
1355 }
1356 suggestionStr += '?';
1357 }
1358 return suggestionStr;
1359}
1360
1364static std::string unescape(std::string_view escaped)
1365{
1366 auto unescaped = std::string();
1367 auto onEscaping = false;
1368 unescaped.reserve(escaped.size());
1369 for (const auto c : escaped) {
1370 if (!onEscaping && c == '\\') {
1371 onEscaping = true;
1372 } else {
1373 unescaped += c;
1374 onEscaping = false;
1375 }
1376 }
1377 return unescaped;
1378}
1379
1385void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1386{
1387 // determine completion info and sort relevant arguments
1388 const auto completionInfo([&] {
1389 auto clutteredCompletionInfo(determineCompletionInfo(argc, argv, currentWordIndex, reader));
1390 clutteredCompletionInfo.relevantArgs.sort(compareArgs);
1391 return clutteredCompletionInfo;
1392 }());
1393
1394 // read the "opening" (started but not finished argument denotation)
1395 const char *opening = nullptr;
1396 string compoundOpening;
1397 size_t openingLen = 0, compoundOpeningStartLen = 0;
1398 unsigned char openingDenotationType = Value;
1399 if (argc && completionInfo.nextArgumentOrValue) {
1400 if (currentWordIndex < static_cast<unsigned int>(argc)) {
1401 opening = argv[currentWordIndex];
1402 // For some reason completions for eg. "set --values disk=1 tag=a" are split so the
1403 // equation sign is an own argument ("set --values disk = 1 tag = a").
1404 // This is not how values are treated by the argument parser. Hence the opening
1405 // must be joined again. In this case only the part after the equation sign needs to be
1406 // provided for completion so compoundOpeningStartLen is set to number of characters to skip.
1407 const size_t minCurrentWordIndex = (completionInfo.lastDetectedArg ? completionInfo.lastDetectedArgIndex : 0);
1408 if (currentWordIndex > minCurrentWordIndex && !strcmp(opening, "=")) {
1409 compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
1410 compoundOpening = argv[currentWordIndex];
1411 compoundOpening += '=';
1412 } else if (currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1], "=")) {
1413 compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
1414 compoundOpening = argv[currentWordIndex];
1415 compoundOpening += '=';
1416 compoundOpening += opening;
1417 }
1418 if (!compoundOpening.empty()) {
1419 opening = compoundOpening.data();
1420 }
1421 } else {
1422 opening = *completionInfo.lastSpecifiedArg;
1423 }
1424 if (*opening == '-') {
1425 ++opening;
1426 ++openingDenotationType;
1427 if (*opening == '-') {
1428 ++opening;
1429 ++openingDenotationType;
1430 }
1431 }
1432 openingLen = strlen(opening);
1433 }
1434
1435 // print "COMPREPLY" bash array
1436 cout << "COMPREPLY=(";
1437 // -> completions for parameter values
1438 bool noWhitespace = false;
1439 for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1440 if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::InvokeCallback && arg->m_callbackFunction) {
1441 arg->m_callbackFunction(arg->isPresent() ? arg->m_occurrences.front() : ArgumentOccurrence(Argument::varValueCount));
1442 }
1443 if (!arg->preDefinedCompletionValues()) {
1444 continue;
1445 }
1446 const bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
1447 if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1448 if (openingDenotationType != Value) {
1449 continue;
1450 }
1451 bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
1452 size_t wordIndex = 0;
1453 for (const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) {
1454 if (wordStart) {
1455 const char *i1 = i, *i2 = opening;
1456 for (; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2)
1457 ;
1458 if ((ok = (i2 == end))) {
1459 cout << '\'';
1460 }
1461 wordStart = false;
1462 wordIndex = 0;
1463 } else if ((wordStart = (*i == ' ') || (*i == '\n'))) {
1464 equationSignAlreadyPresent = false;
1465 if (ok) {
1466 cout << '\'' << ' ';
1467 }
1468 ++i;
1469 continue;
1470 } else if (*i == '=') {
1471 equationSignAlreadyPresent = true;
1472 }
1473 if (!ok) {
1474 ++i;
1475 continue;
1476 }
1477 if (!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
1478 if (*i == '\'') {
1479 cout << "'\"'\"'";
1480 } else {
1481 cout << *i;
1482 }
1483 }
1484 ++i;
1485 ++wordIndex;
1486 switch (*i) {
1487 case ' ':
1488 case '\n':
1489 case '\0':
1490 if (appendEquationSign && !equationSignAlreadyPresent) {
1491 cout << '=';
1492 noWhitespace = true;
1493 equationSignAlreadyPresent = false;
1494 }
1495 if (*i == '\0') {
1496 cout << '\'';
1497 }
1498 }
1499 }
1500 cout << ' ';
1501 } else if (const char *i = arg->preDefinedCompletionValues()) {
1502 bool equationSignAlreadyPresent = false;
1503 cout << '\'';
1504 while (*i) {
1505 if (*i == '\'') {
1506 cout << "'\"'\"'";
1507 } else {
1508 cout << *i;
1509 }
1510 switch (*(++i)) {
1511 case '=':
1512 equationSignAlreadyPresent = true;
1513 break;
1514 case ' ':
1515 case '\n':
1516 case '\0':
1517 if (appendEquationSign && !equationSignAlreadyPresent) {
1518 cout << '=';
1519 equationSignAlreadyPresent = false;
1520 }
1521 if (*i != '\0') {
1522 cout << '\'';
1523 if (*(++i)) {
1524 cout << ' ' << '\'';
1525 }
1526 }
1527 }
1528 }
1529 cout << '\'' << ' ';
1530 }
1531 }
1532 // -> completions for further arguments
1533 for (const Argument *const arg : completionInfo.relevantArgs) {
1534 if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1535 switch (openingDenotationType) {
1536 case Value:
1537 if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1538 continue;
1539 }
1540 break;
1541 case Abbreviation:
1542 break;
1543 case FullName:
1544 if (strncmp(arg->name(), opening, openingLen)) {
1545 continue;
1546 }
1547 }
1548 }
1549
1550 if (opening && openingDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1551 // TODO: add test for this case
1552 cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
1553 } else if (completionInfo.lastDetectedArg && reader.argDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1554 if (reader.argv == reader.end) {
1555 cout << '\'' << *(reader.argv - 1) << '\'' << ' ';
1556 }
1557 } else if (arg->denotesOperation()) {
1558 cout << '\'' << arg->name() << '\'' << ' ';
1559 } else {
1560 cout << '\'' << '-' << '-' << arg->name() << '\'' << ' ';
1561 }
1562 }
1563 // -> completions for files and dirs
1564 // -> if there's already an "opening", determine the dir part and the file part
1565 auto actualDir = std::string(), actualFile = std::string();
1566 auto haveFileOrDirCompletions = false;
1567 if (argc && currentWordIndex == completionInfo.lastSpecifiedArgIndex && opening) {
1568 // the "opening" might contain escaped characters which need to be unescaped first
1569 const auto unescapedOpening = unescape(opening);
1570 // determine the "directory" part
1571 auto dir = directory(unescapedOpening);
1572 if (dir.empty()) {
1573 actualDir = ".";
1574 } else {
1575 if (dir[0] == '\"' || dir[0] == '\'') {
1576 dir.erase(0, 1);
1577 }
1578 if (dir.size() > 1 && (dir[dir.size() - 2] == '\"' || dir[dir.size() - 2] == '\'')) {
1579 dir.erase(dir.size() - 2, 1);
1580 }
1581 actualDir = std::move(dir);
1582 }
1583 // determine the "file" part
1584 auto file = fileName(unescapedOpening);
1585 if (file[0] == '\"' || file[0] == '\'') {
1586 file.erase(0, 1);
1587 }
1588 if (file.size() > 1 && (file[file.size() - 2] == '\"' || file[file.size() - 2] == '\'')) {
1589 file.erase(file.size() - 2, 1);
1590 }
1591 actualFile = std::move(file);
1592 }
1593
1594 // -> completion for files and dirs
1595#ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
1596 if (completionInfo.completeFiles || completionInfo.completeDirs) {
1597 try {
1598 const auto replace = "'"s, with = "'\"'\"'"s;
1599 const auto useActualDir = argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening;
1600 const auto dirEntries = [&] {
1601 filesystem::directory_iterator i;
1602 if (useActualDir) {
1603 i = filesystem::directory_iterator(actualDir);
1604 findAndReplace(actualDir, replace, with);
1605 } else {
1606 i = filesystem::directory_iterator(".");
1607 }
1608 return i;
1609 }();
1610 for (const auto &dirEntry : dirEntries) {
1611 if (!completionInfo.completeDirs && dirEntry.is_directory()) {
1612 continue;
1613 }
1614 if (!completionInfo.completeFiles && !dirEntry.is_directory()) {
1615 continue;
1616 }
1617 auto dirEntryName = dirEntry.path().filename().string();
1618 auto hasStartingQuote = false;
1619 if (useActualDir) {
1620 if (!startsWith(dirEntryName, actualFile)) {
1621 continue;
1622 }
1623 cout << '\'';
1624 hasStartingQuote = true;
1625 if (actualDir != ".") {
1626 cout << actualDir;
1627 }
1628 }
1629 findAndReplace(dirEntryName, replace, with);
1630 if (!hasStartingQuote) {
1631 cout << '\'';
1632 }
1633 cout << dirEntryName << '\'' << ' ';
1634 haveFileOrDirCompletions = true;
1635 }
1636 } catch (const filesystem::filesystem_error &) {
1637 // ignore filesystem errors; there's no good way to report errors when printing bash completion
1638 }
1639 }
1640#endif
1641 cout << ')';
1642
1643 // ensure file or dir completions are formatted appropriately
1644 if (haveFileOrDirCompletions) {
1645 cout << "; compopt -o filenames";
1646 }
1647
1648 // ensure trailing whitespace is omitted
1649 if (noWhitespace) {
1650 cout << "; compopt -o nospace";
1651 }
1652
1653 cout << endl;
1654}
1655
1661{
1662 for (const Argument *arg : args) {
1663 const auto occurrences = arg->occurrences();
1664 if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1665 throw ParseError(argsToString("The argument \"", arg->name(), "\" mustn't be specified more than ", arg->maxOccurrences(),
1666 (arg->maxOccurrences() == 1 ? " time." : " times.")));
1667 }
1668 if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1669 throw ParseError(argsToString("The argument \"", arg->name(), "\" must be specified at least ", arg->minOccurrences(),
1670 (arg->minOccurrences() == 1 ? " time." : " times.")));
1671 }
1672 Argument *conflictingArgument = nullptr;
1673 if (arg->isMainArgument()) {
1674 if (!arg->isCombinable() && arg->isPresent()) {
1675 conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
1676 }
1677 } else {
1678 conflictingArgument = arg->conflictsWithArgument();
1679 }
1680 if (conflictingArgument) {
1681 throw ParseError(argsToString("The argument \"", conflictingArgument->name(), "\" can not be combined with \"", arg->name(), "\"."));
1682 }
1683 for (size_t i = 0; i != occurrences; ++i) {
1684 if (arg->allRequiredValuesPresent(i)) {
1685 continue;
1686 }
1687 stringstream ss(stringstream::in | stringstream::out);
1688 ss << "Not all parameters for argument \"" << arg->name() << "\" ";
1689 if (i) {
1690 ss << " (" << (i + 1) << " occurrence) ";
1691 }
1692 ss << "provided. You have to provide the following parameters:";
1693 size_t valueNamesPrint = 0;
1694 for (const auto &name : arg->m_valueNames) {
1695 ss << ' ' << name;
1696 ++valueNamesPrint;
1697 }
1698 if (arg->m_requiredValueCount != Argument::varValueCount) {
1699 while (valueNamesPrint < arg->m_requiredValueCount) {
1700 ss << "\nvalue " << (++valueNamesPrint);
1701 }
1702 }
1703 throw ParseError(ss.str());
1704 }
1705
1706 // check constraints of sub arguments recursively
1707 checkConstraints(arg->m_subArgs);
1708 }
1709}
1710
1719{
1720 for (const Argument *arg : args) {
1721 // invoke the callback for each occurrence of the argument
1722 if (arg->m_callbackFunction) {
1723 for (const auto &occurrence : arg->m_occurrences) {
1724 arg->m_callbackFunction(occurrence);
1725 }
1726 }
1727 // invoke the callbacks for sub arguments recursively
1728 invokeCallbacks(arg->m_subArgs);
1729 }
1730}
1731
1735void ArgumentParser::invokeExit(int code)
1736{
1737 if (m_exitFunction) {
1738 m_exitFunction(code);
1739 return;
1740 }
1741 std::exit(code);
1742}
1743
1749
1753HelpArgument::HelpArgument(ArgumentParser &parser)
1754 : Argument("help", 'h', "shows this information")
1755{
1756 setCallback([&parser](const ArgumentOccurrence &) {
1758 parser.printHelp(cout);
1759 });
1760}
1761
1766
1772
1791
1797#ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1798 : Argument("no-color", '\0', "disables formatted/colorized output")
1799#else
1800 : Argument("enable-color", '\0', "enables formatted/colorized output")
1801#endif
1802{
1803 setCombinable(true);
1804
1805 // set the environment variable (not directly used and just assigned for printing help)
1806 setEnvironmentVariable("ENABLE_ESCAPE_CODES");
1807
1808 // initialize EscapeCodes::enabled from environment variable
1809 const auto escapeCodesEnabled = isEnvVariableSet(environmentVariable());
1810 if (escapeCodesEnabled.has_value()) {
1811 EscapeCodes::enabled = escapeCodesEnabled.value();
1812 }
1813}
1814
1819{
1820 if (isPresent()) {
1821#ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1822 EscapeCodes::enabled = false;
1823#else
1824 EscapeCodes::enabled = true;
1825#endif
1826 }
1827}
1828
1832void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(const std::vector<Argument *> &argumentPath) const
1833{
1834 throw ParseError(argumentPath.empty()
1835 ? argsToString("Conversion of top-level value \"", valueToConvert, "\" to type \"", targetTypeName, "\" failed: ", errorMessage)
1836 : argsToString("Conversion of value \"", valueToConvert, "\" (for argument --", argumentPath.back()->name(), ") to type \"",
1837 targetTypeName, "\" failed: ", errorMessage));
1838}
1839
1843void ArgumentOccurrence::throwNumberOfValuesNotSufficient(unsigned long valuesToConvert) const
1844{
1845 throw ParseError(path.empty()
1846 ? argsToString("Expected ", valuesToConvert, " top-level values to be present but only ", values.size(), " have been specified.")
1847 : argsToString("Expected ", valuesToConvert, " values for argument --", path.back()->name(), " to be present but only ", values.size(),
1848 " have been specified."));
1849}
1850
1851} // namespace CppUtilities
#define CPP_UTILITIES_IF_DEBUG_BUILD(x)
Wraps debug-only lines conveniently.
Definition global.h:118
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.
void ensureDefaultOperation()
Ensures a main operation argument is present.
Argument * specifiedOperation() const
Returns the first operation argument specified by the user or nullptr if no operation has been specif...
void assumeDefaultArgument()
Assumes the default argument is present (if not already present).
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.
void addSubArguments(const ArgumentInitializerList &subArguments)
Sets the secondary arguments for this argument.
ValueCompletionBehavior valueCompletionBehaviour() const
Returns the items to be considered when generating completion for the 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 maxOccurrences() const
Returns the maximum number of occurrences.
Argument::Flags flags() const
Returns Argument::Flags for the argument.
const char * preDefinedCompletionValues() const
Returns the assigned values used when generating completion for the values.
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.
std::size_t minOccurrences() const
Returns the minimum number of occurrences.
bool allRequiredValuesPresent(std::size_t occurrence=0) const
Returns an indication whether all required values are present.
void setSubArguments(const ArgumentInitializerList &subArguments)
Sets the secondary arguments for this argument.
void setCombinable(bool combinable)
Sets whether this argument can be combined.
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.
void setEnvironmentVariable(const char *environmentVariable)
Sets the environment variable queried when firstValue() is called.
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...
Wrapper(const char *str, Indentation currentIndentation=Indentation())
#define CMD_UTILS_START_CONSOLE
#define CPP_UTILITIES_EXPORT
Marks the symbol to be exported by the c++utilities library.
Definition global.h:14
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::initializer_list< Argument * > ArgumentInitializerList
Argument * firstPresentUncombinableArg(const ArgumentVector &args, const Argument *except)
This function return the first present and uncombinable argument of the given list of arguments.
std::vector< Argument * > ArgumentVector
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.
IntegralType stringToNumber(const StringType &string, BaseType base=10)
Converts the given string to an unsigned/signed number assuming string uses the specified base.
ParseArgumentBehavior
The ParseArgumentBehavior enum specifies the behavior when parsing arguments.
bool operator==(const AsHexNumber< T > &lhs, const AsHexNumber< T > &rhs)
Provides operator == required by CPPUNIT_ASSERT_EQUAL.
Definition testutils.h:241
StringType argsToString(Args &&...args)
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.
CPP_UTILITIES_EXPORT 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.
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.
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