C++ Utilities 5.22.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
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
38enum ArgumentDenotationType : unsigned char {
39 Value = 0,
41 FullName = 2
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
66 : lastDetectedArg(reader.lastArg)
67{
68}
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
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)
134 , completionMode(completionMode)
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 an 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 read(lastArg->m_subArgs);
365 argDenotation = nullptr;
366 continue;
367 }
368
369 // argument denotation is unknown -> handle error
370 if (parentArg) {
371 // continue with parent level
372 return false;
373 }
374 if (completionMode) {
375 // ignore unknown denotation
376 ++index;
377 ++argv;
378 argDenotation = nullptr;
379 } else {
380 switch (parser.m_unknownArgBehavior) {
382 cerr << Phrases::Warning << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << Phrases::EndFlush;
383 [[fallthrough]];
385 // ignore unknown denotation
386 ++index;
387 ++argv;
388 argDenotation = nullptr;
389 break;
391 return false;
392 }
393 }
394 } // while(argv != end)
395 return true;
396}
397
404ostream &operator<<(ostream &os, const Wrapper &wrapper)
405{
406 // determine max. number of columns
407 static const TerminalSize termSize(determineTerminalSize());
408 const auto maxColumns = termSize.columns ? termSize.columns : numeric_limits<unsigned short>::max();
409
410 // print wrapped string considering indentation
411 unsigned short currentCol = wrapper.m_indentation.level;
412 for (const char *currentChar = wrapper.m_str; *currentChar; ++currentChar) {
413 const bool wrappingRequired = currentCol >= maxColumns;
414 if (wrappingRequired || *currentChar == '\n') {
415 // insert newline (TODO: wrap only at end of a word)
416 os << '\n';
417 // print indentation (if enough space)
418 if (wrapper.m_indentation.level < maxColumns) {
419 os << wrapper.m_indentation;
420 currentCol = wrapper.m_indentation.level;
421 } else {
422 currentCol = 0;
423 }
424 }
425 if (*currentChar != '\n' && (!wrappingRequired || *currentChar != ' ')) {
426 os << *currentChar;
427 ++currentCol;
428 }
429 }
430 return os;
431}
432
434
436
437inline bool notEmpty(const char *str)
438{
439 return str && *str;
440}
441
443
460Argument::Argument(const char *name, char abbreviation, const char *description, const char *example)
461 : m_name(name)
462 , m_abbreviation(abbreviation)
463 , m_environmentVar(nullptr)
464 , m_description(description)
465 , m_example(example)
466 , m_minOccurrences(0)
467 , m_maxOccurrences(1)
468 , m_requiredValueCount(0)
469 , m_flags(Flags::None)
470 , m_deprecatedBy(nullptr)
471 , m_isMainArg(false)
474 , m_preDefinedCompletionValues(nullptr)
475{
476}
477
482{
483}
484
492const char *Argument::firstValue() const
493{
494 if (!m_occurrences.empty() && !m_occurrences.front().values.empty()) {
495 return m_occurrences.front().values.front();
496 } else if (m_environmentVar) {
497 return getenv(m_environmentVar);
498 } else {
499 return nullptr;
500 }
501}
502
506const char *Argument::firstValueOr(const char *fallback) const
507{
508 if (const auto *const v = firstValue()) {
509 return v;
510 } else {
511 return fallback;
512 }
513}
514
518void Argument::printInfo(ostream &os, unsigned char indentation) const
519{
520 if (isDeprecated()) {
521 return;
522 }
523 Indentation ident(indentation);
524 os << ident;
526 if (notEmpty(name())) {
527 if (!denotesOperation()) {
528 os << '-' << '-';
529 }
530 os << name();
531 }
532 if (notEmpty(name()) && abbreviation()) {
533 os << ',' << ' ';
534 }
535 if (abbreviation()) {
536 os << '-' << abbreviation();
537 }
539 if (requiredValueCount()) {
540 unsigned int valueNamesPrint = 0;
541 for (auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
542 os << ' ' << '[' << *i << ']';
543 ++valueNamesPrint;
544 }
546 os << " ...";
547 } else {
548 for (; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
549 os << " [value " << (valueNamesPrint + 1) << ']';
550 }
551 }
552 }
553 ident.level += 2;
554 if (notEmpty(description())) {
555 os << '\n' << ident << Wrapper(description(), ident);
556 }
557 if (isRequired()) {
558 os << '\n' << ident << "particularities: mandatory";
559 if (!isMainArgument()) {
560 os << " if parent argument is present";
561 }
562 }
563 if (environmentVariable()) {
564 os << '\n' << ident << "default environment variable: " << Wrapper(environmentVariable(), ident + 30);
565 }
566 os << '\n';
567 bool hasSubArgs = false;
568 for (const auto *const arg : subArguments()) {
569 if (arg->isDeprecated()) {
570 continue;
571 }
572 hasSubArgs = true;
573 arg->printInfo(os, ident.level);
574 }
575 if (notEmpty(example())) {
576 if (ident.level == 2 && hasSubArgs) {
577 os << '\n';
578 }
579 os << ident << "example: " << Wrapper(example(), ident + 9);
580 os << '\n';
581 }
582}
583
590{
591 for (Argument *arg : args) {
592 if (arg != except && arg->isPresent() && !arg->isCombinable()) {
593 return arg;
594 }
595 }
596 return nullptr;
597}
598
615{
616 // remove this argument from the parents list of the previous secondary arguments
617 for (Argument *const arg : m_subArgs) {
618 arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(), this), arg->m_parents.end());
619 }
620 // clear currently assigned args before adding new ones
621 m_subArgs.clear();
623}
624
641{
642 // append secondary arguments
643 const auto requiredCap = m_subArgs.size() + subArguments.size();
644 if (requiredCap < m_subArgs.capacity()) {
645 m_subArgs.reserve(requiredCap); // does insert this for us?
646 }
647 m_subArgs.insert(m_subArgs.end(), subArguments.begin(), subArguments.end());
648 // add this argument to the parents list of the assigned secondary arguments and set the parser
649 for (Argument *const arg : subArguments) {
650 if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
651 arg->m_parents.push_back(this);
652 }
653 }
654}
655
664{
665 if (find(m_subArgs.cbegin(), m_subArgs.cend(), arg) != m_subArgs.cend()) {
666 return;
667 }
668 m_subArgs.push_back(arg);
669 if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
670 arg->m_parents.push_back(this);
671 }
672}
673
679{
680 if (isMainArgument()) {
681 return true;
682 }
683 for (const Argument *parent : m_parents) {
684 if (parent->isPresent()) {
685 return true;
686 }
687 }
688 return false;
689}
690
700{
701 return isPresent() ? wouldConflictWithArgument() : nullptr;
702}
703
713{
714 if (isCombinable()) {
715 return nullptr;
716 }
717 for (Argument *parent : m_parents) {
718 for (Argument *sibling : parent->subArguments()) {
719 if (sibling != this && sibling->isPresent() && !sibling->isCombinable()) {
720 return sibling;
721 }
722 }
723 }
724 return nullptr;
725}
726
732{
733 for (Argument *arg : m_subArgs) {
734 if (arg->denotesOperation() && arg->isPresent()) {
735 return arg;
736 }
737 }
738 return nullptr;
739}
740
746{
747 for (Argument *arg : m_subArgs) {
748 arg->resetRecursively();
749 }
750 reset();
751}
752
770 : m_actualArgc(0)
771 , m_executable(nullptr)
772 , m_unknownArgBehavior(UnknownArgumentBehavior::Fail)
773 , m_defaultArg(nullptr)
774 , m_helpArg(*this)
775{
776}
777
788{
789 if (!mainArguments.size()) {
790 m_mainArgs.clear();
791 return;
792 }
793 for (Argument *arg : mainArguments) {
794 arg->m_isMainArg = true;
795 }
796 m_mainArgs.assign(mainArguments);
797 if (m_defaultArg || (*mainArguments.begin())->requiredValueCount()) {
798 return;
799 }
800 bool subArgsRequired = false;
801 for (const Argument *subArg : (*mainArguments.begin())->subArguments()) {
802 if (subArg->isRequired()) {
803 subArgsRequired = true;
804 break;
805 }
806 }
807 if (!subArgsRequired) {
808 m_defaultArg = *mainArguments.begin();
809 }
810}
811
819{
820 argument->m_isMainArg = true;
821 m_mainArgs.push_back(argument);
822}
823
827void ArgumentParser::printHelp(ostream &os) const
828{
830 bool wroteLine = false;
832 os << applicationInfo.name;
834 os << ',' << ' ';
835 }
836 wroteLine = true;
837 }
839 os << "version " << applicationInfo.version;
840 wroteLine = true;
841 }
842 if (wroteLine) {
843 os << '\n' << '\n';
844 }
846
849 wroteLine = true;
850 }
851 if (wroteLine) {
852 os << '\n' << '\n';
853 }
854
855 if (!m_mainArgs.empty()) {
856 bool hasOperations = false, hasTopLevelOptions = false;
857 for (const Argument *const arg : m_mainArgs) {
858 if (arg->denotesOperation()) {
859 hasOperations = true;
860 } else if (strcmp(arg->name(), "help")) {
861 hasTopLevelOptions = true;
862 }
863 if (hasOperations && hasTopLevelOptions) {
864 break;
865 }
866 }
867
868 // check whether operations are available
869 if (hasOperations) {
870 // split top-level operations and other configurations
871 os << "Available operations:";
872 for (const Argument *const arg : m_mainArgs) {
873 if (!arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(), "help")) {
874 continue;
875 }
876 os << '\n';
877 arg->printInfo(os);
878 }
879 if (hasTopLevelOptions) {
880 os << "\nAvailable top-level options:";
881 for (const Argument *const arg : m_mainArgs) {
882 if (arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(), "help")) {
883 continue;
884 }
885 os << '\n';
886 arg->printInfo(os);
887 }
888 }
889 } else {
890 // just show all args if no operations are available
891 os << "Available arguments:";
892 for (const Argument *const arg : m_mainArgs) {
893 if (arg->isDeprecated() || !strcmp(arg->name(), "help")) {
894 continue;
895 }
896 os << '\n';
897 arg->printInfo(os);
898 }
899 }
900 }
901
902 if (!applicationInfo.dependencyVersions.empty()) {
903 os << '\n';
905 os << "Linked against: " << *i;
906 for (++i; i != end; ++i) {
907 os << ',' << ' ' << *i;
908 }
909 os << '\n';
910 }
911
913 os << "\nProject website: " << applicationInfo.url << endl;
914 }
915}
916
933void ArgumentParser::parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior)
934{
935 try {
936 readArgs(argc, argv);
937 if (!argc) {
938 return;
939 }
941 checkConstraints(m_mainArgs);
942 }
944 invokeCallbacks(m_mainArgs);
945 }
946 } catch (const ParseError &failure) {
949 cerr << failure;
950 invokeExit(EXIT_FAILURE);
951 }
952 throw;
953 }
954}
955
969void ArgumentParser::readArgs(int argc, const char *const *argv)
970{
971 CPP_UTILITIES_IF_DEBUG_BUILD(verifyArgs(m_mainArgs);)
972 m_actualArgc = 0;
973
974 // the first argument is the executable name
975 if (!argc) {
976 m_executable = nullptr;
977 return;
978 }
979 m_executable = *argv;
980
981 // check for further arguments
982 if (!--argc) {
983 // no arguments specified -> flag default argument as present if one is assigned
984 if (m_defaultArg) {
985 m_defaultArg->m_occurrences.emplace_back(0);
986 }
987 return;
988 }
989
990 // check for completion mode: if first arg (after executable name) is "--bash-completion-for", bash completion for the following arguments is requested
991 const bool completionMode = !strcmp(*++argv, "--bash-completion-for");
992
993 // determine the index of the current word for completion and the number of arguments to be passed to ArgumentReader
994 unsigned int currentWordIndex = 0, argcForReader;
995 if (completionMode) {
996 // the first argument after "--bash-completion-for" is the index of the current word
997 try {
998 currentWordIndex = (--argc ? stringToNumber<unsigned int, string>(*(++argv)) : 0);
999 if (argc) {
1000 ++argv;
1001 --argc;
1002 }
1003 } catch (const ConversionException &) {
1004 currentWordIndex = static_cast<unsigned int>(argc - 1);
1005 }
1006 argcForReader = min(static_cast<unsigned int>(argc), currentWordIndex + 1);
1007 } else {
1008 argcForReader = static_cast<unsigned int>(argc);
1009 }
1010
1011 // read specified arguments
1012 ArgumentReader reader(*this, argv, argv + argcForReader, completionMode);
1013 const bool allArgsProcessed(reader.read());
1014 m_noColorArg.apply();
1015
1016 // fail when not all arguments could be processed, except when in completion mode
1017 if (!completionMode && !allArgsProcessed) {
1018 const auto suggestions(findSuggestions(argc, argv, static_cast<unsigned int>(argc - 1), reader));
1019 throw ParseError(argsToString("The specified argument \"", *reader.argv, "\" is unknown.", suggestions));
1020 }
1021
1022 // print Bash completion and prevent the application to continue with the regular execution
1023 if (completionMode) {
1024 printBashCompletion(argc, argv, currentWordIndex, reader);
1025 invokeExit(EXIT_SUCCESS);
1026 }
1027}
1028
1034{
1035 for (Argument *arg : m_mainArgs) {
1036 arg->resetRecursively();
1037 }
1038 m_actualArgc = 0;
1039}
1040
1047{
1048 for (Argument *arg : m_mainArgs) {
1049 if (arg->denotesOperation() && arg->isPresent()) {
1050 return arg;
1051 }
1052 }
1053 return nullptr;
1054}
1055
1060{
1061 for (const Argument *arg : m_mainArgs) {
1062 if (!arg->isCombinable() && arg->isPresent()) {
1063 return true;
1064 }
1065 }
1066 return false;
1067}
1068
1069#ifdef CPP_UTILITIES_DEBUG_BUILD
1084void ArgumentParser::verifyArgs(const ArgumentVector &args)
1085{
1086 vector<const Argument *> verifiedArgs;
1087 verifiedArgs.reserve(args.size());
1088 vector<char> abbreviations;
1089 abbreviations.reserve(abbreviations.size() + args.size());
1090 vector<const char *> names;
1091 names.reserve(names.size() + args.size());
1092 bool hasImplicit = false;
1093 for (const Argument *arg : args) {
1094 assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
1095 verifiedArgs.push_back(arg);
1096 assert(!arg->isImplicit() || !hasImplicit);
1097 hasImplicit |= arg->isImplicit();
1098 assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
1099 abbreviations.push_back(arg->abbreviation());
1100 assert(!arg->name() || find_if(names.cbegin(), names.cend(), [arg](const char *name) { return !strcmp(arg->name(), name); }) == names.cend());
1101 assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0 || (arg->flags() & Argument::Flags::Greedy));
1102 names.emplace_back(arg->name());
1103 }
1104 for (const Argument *arg : args) {
1105 verifyArgs(arg->subArguments());
1106 }
1107}
1108#endif
1109
1117bool compareArgs(const Argument *arg1, const Argument *arg2)
1118{
1119 if (arg1->denotesOperation() && !arg2->denotesOperation()) {
1120 return true;
1121 } else if (!arg1->denotesOperation() && arg2->denotesOperation()) {
1122 return false;
1123 } else {
1124 return strcmp(arg1->name(), arg2->name()) < 0;
1125 }
1126}
1127
1132void insertSiblings(const ArgumentVector &siblings, list<const Argument *> &target)
1133{
1134 bool onlyCombinable = false;
1135 for (const Argument *sibling : siblings) {
1136 if (sibling->isPresent() && !sibling->isCombinable()) {
1137 onlyCombinable = true;
1138 break;
1139 }
1140 }
1141 for (const Argument *sibling : siblings) {
1142 if ((!onlyCombinable || sibling->isCombinable()) && sibling->occurrences() < sibling->maxOccurrences()) {
1143 target.push_back(sibling);
1144 }
1145 }
1146}
1147
1151ArgumentCompletionInfo ArgumentParser::determineCompletionInfo(
1152 int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1153{
1154 ArgumentCompletionInfo completion(reader);
1155
1156 // determine last detected arg
1157 if (completion.lastDetectedArg) {
1158 completion.lastDetectedArgIndex = static_cast<size_t>(reader.lastArgDenotation - argv);
1159 completion.lastDetectedArgPath = completion.lastDetectedArg->path(completion.lastDetectedArg->occurrences() - 1);
1160 }
1161
1162 // determine last arg, omitting trailing empty args
1163 if (argc) {
1164 completion.lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
1165 completion.lastSpecifiedArg = argv + completion.lastSpecifiedArgIndex;
1166 for (; completion.lastSpecifiedArg >= argv && **completion.lastSpecifiedArg == '\0';
1167 --completion.lastSpecifiedArg, --completion.lastSpecifiedArgIndex)
1168 ;
1169 }
1170
1171 // just return main arguments if no args detected
1172 if (!completion.lastDetectedArg || !completion.lastDetectedArg->isPresent()) {
1173 completion.nextArgumentOrValue = true;
1174 insertSiblings(m_mainArgs, completion.relevantArgs);
1175 completion.relevantArgs.sort(compareArgs);
1176 return completion;
1177 }
1178
1179 completion.nextArgumentOrValue = currentWordIndex > completion.lastDetectedArgIndex;
1180 if (!completion.nextArgumentOrValue) {
1181 // since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
1182 completion.relevantArgs.push_back(completion.lastDetectedArg);
1183 completion.relevantArgs.sort(compareArgs);
1184 return completion;
1185 }
1186
1187 // define function to add parameter values of argument as possible completions
1188 const auto addValueCompletionsForArg = [&completion](const Argument *arg) {
1189 if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) {
1190 completion.relevantPreDefinedValues.push_back(arg);
1191 }
1192 if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues) || !arg->preDefinedCompletionValues()) {
1193 completion.completeFiles = completion.completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
1194 completion.completeDirs = completion.completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
1195 }
1196 };
1197
1198 // detect number of specified values
1199 auto currentValueCount = completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size();
1200 // ignore values which are specified after the current word
1201 if (currentValueCount) {
1202 const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - completion.lastDetectedArgIndex;
1203 if (currentValueCount > currentWordIndexRelativeToLastDetectedArg) {
1204 currentValueCount -= currentWordIndexRelativeToLastDetectedArg;
1205 } else {
1206 currentValueCount = 0;
1207 }
1208 }
1209
1210 // add value completions for implicit child if there are no value specified and there are no values required by the
1211 // last detected argument itself
1212 if (!currentValueCount && !completion.lastDetectedArg->requiredValueCount()) {
1213 for (const Argument *child : completion.lastDetectedArg->subArguments()) {
1214 if (child->isImplicit() && child->requiredValueCount()) {
1215 addValueCompletionsForArg(child);
1216 break;
1217 }
1218 }
1219 }
1220
1221 // add value completions for last argument if there are further values required
1222 if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1223 || (currentValueCount < completion.lastDetectedArg->requiredValueCount())) {
1224 addValueCompletionsForArg(completion.lastDetectedArg);
1225 }
1226
1227 if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1228 || completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size()
1229 >= completion.lastDetectedArg->requiredValueCount()) {
1230 // sub arguments of the last arg are possible completions
1231 for (const Argument *subArg : completion.lastDetectedArg->subArguments()) {
1232 if (subArg->occurrences() < subArg->maxOccurrences()) {
1233 completion.relevantArgs.push_back(subArg);
1234 }
1235 }
1236
1237 // siblings of parents are possible completions as well
1238 for (auto parentArgument = completion.lastDetectedArgPath.crbegin(), end = completion.lastDetectedArgPath.crend();; ++parentArgument) {
1239 insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, completion.relevantArgs);
1240 if (parentArgument == end) {
1241 break;
1242 }
1243 }
1244 }
1245
1246 return completion;
1247}
1248
1252string ArgumentParser::findSuggestions(int argc, const char *const *argv, unsigned int cursorPos, const ArgumentReader &reader) const
1253{
1254 // determine completion info
1255 const auto completionInfo(determineCompletionInfo(argc, argv, cursorPos, reader));
1256
1257 // determine the unknown/misspelled argument
1258 const auto *unknownArg(*reader.argv);
1259 auto unknownArgSize(strlen(unknownArg));
1260 // -> refuse suggestions for long args to prevent huge memory allocation for Damerau-Levenshtein algo
1261 if (unknownArgSize > 16) {
1262 return string();
1263 }
1264 // -> remove dashes since argument names internally don't have them
1265 if (unknownArgSize >= 2 && unknownArg[0] == '-' && unknownArg[1] == '-') {
1266 unknownArg += 2;
1267 unknownArgSize -= 2;
1268 }
1269
1270 // find best suggestions limiting the results to 2
1271 multiset<ArgumentSuggestion> bestSuggestions;
1272 // -> consider relevant arguments
1273 for (const Argument *const arg : completionInfo.relevantArgs) {
1274 ArgumentSuggestion(unknownArg, unknownArgSize, arg->name(), !arg->denotesOperation()).addTo(bestSuggestions, 2);
1275 }
1276 // -> consider relevant values
1277 for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1278 if (!arg->preDefinedCompletionValues()) {
1279 continue;
1280 }
1281 for (const char *i = arg->preDefinedCompletionValues(); *i; ++i) {
1282 const char *const wordStart(i);
1283 const char *wordEnd(wordStart + 1);
1284 for (; *wordEnd && *wordEnd != ' '; ++wordEnd)
1285 ;
1286 ArgumentSuggestion(unknownArg, unknownArgSize, wordStart, static_cast<size_t>(wordEnd - wordStart), false).addTo(bestSuggestions, 2);
1287 i = wordEnd;
1288 }
1289 }
1290
1291 // format suggestion
1292 string suggestionStr;
1293 if (const auto suggestionCount = bestSuggestions.size()) {
1294 // allocate memory
1295 size_t requiredSize = 15;
1296 for (const auto &suggestion : bestSuggestions) {
1297 requiredSize += suggestion.suggestionSize + 2;
1298 if (suggestion.hasDashPrefix) {
1299 requiredSize += 2;
1300 }
1301 }
1302 suggestionStr.reserve(requiredSize);
1303
1304 // add each suggestion to end up with something like "Did you mean status (1), pause (3), cat (4), edit (5) or rescan-all (8)?"
1305 suggestionStr += "\nDid you mean ";
1306 size_t i = 0;
1307 for (const auto &suggestion : bestSuggestions) {
1308 if (++i == suggestionCount && suggestionCount != 1) {
1309 suggestionStr += " or ";
1310 } else if (i > 1) {
1311 suggestionStr += ", ";
1312 }
1313 if (suggestion.hasDashPrefix) {
1314 suggestionStr += "--";
1315 }
1316 suggestionStr.append(suggestion.suggestion, suggestion.suggestionSize);
1317 }
1318 suggestionStr += '?';
1319 }
1320 return suggestionStr;
1321}
1322
1328void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1329{
1330 // determine completion info and sort relevant arguments
1331 const auto completionInfo([&] {
1332 auto clutteredCompletionInfo(determineCompletionInfo(argc, argv, currentWordIndex, reader));
1333 clutteredCompletionInfo.relevantArgs.sort(compareArgs);
1334 return clutteredCompletionInfo;
1335 }());
1336
1337 // read the "opening" (started but not finished argument denotation)
1338 const char *opening = nullptr;
1339 string compoundOpening;
1340 size_t openingLen = 0, compoundOpeningStartLen = 0;
1341 unsigned char openingDenotationType = Value;
1342 if (argc && completionInfo.nextArgumentOrValue) {
1343 if (currentWordIndex < static_cast<unsigned int>(argc)) {
1344 opening = argv[currentWordIndex];
1345 // For some reason completions for eg. "set --values disk=1 tag=a" are split so the
1346 // equation sign is an own argument ("set --values disk = 1 tag = a").
1347 // This is not how values are treated by the argument parser. Hence the opening
1348 // must be joined again. In this case only the part after the equation sign needs to be
1349 // provided for completion so compoundOpeningStartLen is set to number of characters to skip.
1350 const size_t minCurrentWordIndex = (completionInfo.lastDetectedArg ? completionInfo.lastDetectedArgIndex : 0);
1351 if (currentWordIndex > minCurrentWordIndex && !strcmp(opening, "=")) {
1352 compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
1353 compoundOpening = argv[currentWordIndex];
1354 compoundOpening += '=';
1355 } else if (currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1], "=")) {
1356 compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
1357 compoundOpening = argv[currentWordIndex];
1358 compoundOpening += '=';
1359 compoundOpening += opening;
1360 }
1361 if (!compoundOpening.empty()) {
1362 opening = compoundOpening.data();
1363 }
1364 } else {
1365 opening = *completionInfo.lastSpecifiedArg;
1366 }
1367 if (*opening == '-') {
1368 ++opening;
1369 ++openingDenotationType;
1370 if (*opening == '-') {
1371 ++opening;
1372 ++openingDenotationType;
1373 }
1374 }
1375 openingLen = strlen(opening);
1376 }
1377
1378 // print "COMPREPLY" bash array
1379 cout << "COMPREPLY=(";
1380 // -> completions for parameter values
1381 bool noWhitespace = false;
1382 for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1383 if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::InvokeCallback && arg->m_callbackFunction) {
1384 arg->m_callbackFunction(arg->isPresent() ? arg->m_occurrences.front() : ArgumentOccurrence(Argument::varValueCount));
1385 }
1386 if (!arg->preDefinedCompletionValues()) {
1387 continue;
1388 }
1389 const bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
1390 if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1391 if (openingDenotationType != Value) {
1392 continue;
1393 }
1394 bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
1395 size_t wordIndex = 0;
1396 for (const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) {
1397 if (wordStart) {
1398 const char *i1 = i, *i2 = opening;
1399 for (; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2)
1400 ;
1401 if ((ok = (i2 == end))) {
1402 cout << '\'';
1403 }
1404 wordStart = false;
1405 wordIndex = 0;
1406 } else if ((wordStart = (*i == ' ') || (*i == '\n'))) {
1407 equationSignAlreadyPresent = false;
1408 if (ok) {
1409 cout << '\'' << ' ';
1410 }
1411 ++i;
1412 continue;
1413 } else if (*i == '=') {
1414 equationSignAlreadyPresent = true;
1415 }
1416 if (!ok) {
1417 ++i;
1418 continue;
1419 }
1420 if (!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
1421 if (*i == '\'') {
1422 cout << "'\"'\"'";
1423 } else {
1424 cout << *i;
1425 }
1426 }
1427 ++i;
1428 ++wordIndex;
1429 switch (*i) {
1430 case ' ':
1431 case '\n':
1432 case '\0':
1433 if (appendEquationSign && !equationSignAlreadyPresent) {
1434 cout << '=';
1435 noWhitespace = true;
1436 equationSignAlreadyPresent = false;
1437 }
1438 if (*i == '\0') {
1439 cout << '\'';
1440 }
1441 }
1442 }
1443 cout << ' ';
1444 } else if (const char *i = arg->preDefinedCompletionValues()) {
1445 bool equationSignAlreadyPresent = false;
1446 cout << '\'';
1447 while (*i) {
1448 if (*i == '\'') {
1449 cout << "'\"'\"'";
1450 } else {
1451 cout << *i;
1452 }
1453 switch (*(++i)) {
1454 case '=':
1455 equationSignAlreadyPresent = true;
1456 break;
1457 case ' ':
1458 case '\n':
1459 case '\0':
1460 if (appendEquationSign && !equationSignAlreadyPresent) {
1461 cout << '=';
1462 equationSignAlreadyPresent = false;
1463 }
1464 if (*i != '\0') {
1465 cout << '\'';
1466 if (*(++i)) {
1467 cout << ' ' << '\'';
1468 }
1469 }
1470 }
1471 }
1472 cout << '\'' << ' ';
1473 }
1474 }
1475 // -> completions for further arguments
1476 for (const Argument *const arg : completionInfo.relevantArgs) {
1477 if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1478 switch (openingDenotationType) {
1479 case Value:
1480 if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1481 continue;
1482 }
1483 break;
1484 case Abbreviation:
1485 break;
1486 case FullName:
1487 if (strncmp(arg->name(), opening, openingLen)) {
1488 continue;
1489 }
1490 }
1491 }
1492
1493 if (opening && openingDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1494 // TODO: add test for this case
1495 cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
1496 } else if (completionInfo.lastDetectedArg && reader.argDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1497 if (reader.argv == reader.end) {
1498 cout << '\'' << *(reader.argv - 1) << '\'' << ' ';
1499 }
1500 } else if (arg->denotesOperation()) {
1501 cout << '\'' << arg->name() << '\'' << ' ';
1502 } else {
1503 cout << '\'' << '-' << '-' << arg->name() << '\'' << ' ';
1504 }
1505 }
1506 // -> completions for files and dirs
1507 // -> if there's already an "opening", determine the dir part and the file part
1508 string actualDir, actualFile;
1509 bool haveFileOrDirCompletions = false;
1510 if (argc && currentWordIndex == completionInfo.lastSpecifiedArgIndex && opening) {
1511 // the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
1512 string unescapedOpening(opening);
1513 findAndReplace<string>(unescapedOpening, "\\ ", " ");
1514 findAndReplace<string>(unescapedOpening, "\\,", ",");
1515 findAndReplace<string>(unescapedOpening, "\\[", "[");
1516 findAndReplace<string>(unescapedOpening, "\\]", "]");
1517 findAndReplace<string>(unescapedOpening, "\\!", "!");
1518 findAndReplace<string>(unescapedOpening, "\\#", "#");
1519 findAndReplace<string>(unescapedOpening, "\\$", "$");
1520 findAndReplace<string>(unescapedOpening, "\\'", "'");
1521 findAndReplace<string>(unescapedOpening, "\\\"", "\"");
1522 findAndReplace<string>(unescapedOpening, "\\\\", "\\");
1523 // determine the "directory" part
1524 string dir = directory(unescapedOpening);
1525 if (dir.empty()) {
1526 actualDir = ".";
1527 } else {
1528 if (dir[0] == '\"' || dir[0] == '\'') {
1529 dir.erase(0, 1);
1530 }
1531 if (dir.size() > 1 && (dir[dir.size() - 2] == '\"' || dir[dir.size() - 2] == '\'')) {
1532 dir.erase(dir.size() - 2, 1);
1533 }
1534 actualDir = std::move(dir);
1535 }
1536 // determine the "file" part
1537 string file = fileName(unescapedOpening);
1538 if (file[0] == '\"' || file[0] == '\'') {
1539 file.erase(0, 1);
1540 }
1541 if (file.size() > 1 && (file[file.size() - 2] == '\"' || file[file.size() - 2] == '\'')) {
1542 file.erase(file.size() - 2, 1);
1543 }
1544 actualFile = std::move(file);
1545 }
1546
1547 // -> completion for files and dirs
1548#ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
1549 if (completionInfo.completeFiles || completionInfo.completeDirs) {
1550 try {
1551 const auto replace = "'"s, with = "'\"'\"'"s;
1552 const auto useActualDir = argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening;
1553 const auto dirEntries = [&] {
1554 filesystem::directory_iterator i;
1555 if (useActualDir) {
1556 i = filesystem::directory_iterator(actualDir);
1557 findAndReplace(actualDir, replace, with);
1558 } else {
1559 i = filesystem::directory_iterator(".");
1560 }
1561 return i;
1562 }();
1563 for (const auto &dirEntry : dirEntries) {
1564 if (!completionInfo.completeDirs && dirEntry.is_directory()) {
1565 continue;
1566 }
1567 if (!completionInfo.completeFiles && !dirEntry.is_directory()) {
1568 continue;
1569 }
1570 auto dirEntryName = dirEntry.path().filename().string();
1571 auto hasStartingQuote = false;
1572 if (useActualDir) {
1573 if (!startsWith(dirEntryName, actualFile)) {
1574 continue;
1575 }
1576 cout << '\'';
1577 hasStartingQuote = true;
1578 if (actualDir != ".") {
1579 cout << actualDir;
1580 }
1581 }
1582 findAndReplace(dirEntryName, replace, with);
1583 if (!hasStartingQuote) {
1584 cout << '\'';
1585 }
1586 cout << dirEntryName << '\'' << ' ';
1587 haveFileOrDirCompletions = true;
1588 }
1589 } catch (const filesystem::filesystem_error &) {
1590 // ignore filesystem errors; there's no good way to report errors when printing bash completion
1591 }
1592 }
1593#endif
1594 cout << ')';
1595
1596 // ensure file or dir completions are formatted appropriately
1597 if (haveFileOrDirCompletions) {
1598 cout << "; compopt -o filenames";
1599 }
1600
1601 // ensure trailing whitespace is omitted
1602 if (noWhitespace) {
1603 cout << "; compopt -o nospace";
1604 }
1605
1606 cout << endl;
1607}
1608
1614{
1615 for (const Argument *arg : args) {
1616 const auto occurrences = arg->occurrences();
1617 if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1618 throw ParseError(argsToString("The argument \"", arg->name(), "\" mustn't be specified more than ", arg->maxOccurrences(),
1619 (arg->maxOccurrences() == 1 ? " time." : " times.")));
1620 }
1621 if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1622 throw ParseError(argsToString("The argument \"", arg->name(), "\" must be specified at least ", arg->minOccurrences(),
1623 (arg->minOccurrences() == 1 ? " time." : " times.")));
1624 }
1625 Argument *conflictingArgument = nullptr;
1626 if (arg->isMainArgument()) {
1627 if (!arg->isCombinable() && arg->isPresent()) {
1628 conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
1629 }
1630 } else {
1631 conflictingArgument = arg->conflictsWithArgument();
1632 }
1633 if (conflictingArgument) {
1634 throw ParseError(argsToString("The argument \"", conflictingArgument->name(), "\" can not be combined with \"", arg->name(), "\"."));
1635 }
1636 for (size_t i = 0; i != occurrences; ++i) {
1637 if (arg->allRequiredValuesPresent(i)) {
1638 continue;
1639 }
1640 stringstream ss(stringstream::in | stringstream::out);
1641 ss << "Not all parameters for argument \"" << arg->name() << "\" ";
1642 if (i) {
1643 ss << " (" << (i + 1) << " occurrence) ";
1644 }
1645 ss << "provided. You have to provide the following parameters:";
1646 size_t valueNamesPrint = 0;
1647 for (const auto &name : arg->m_valueNames) {
1648 ss << ' ' << name;
1649 ++valueNamesPrint;
1650 }
1651 if (arg->m_requiredValueCount != Argument::varValueCount) {
1652 while (valueNamesPrint < arg->m_requiredValueCount) {
1653 ss << "\nvalue " << (++valueNamesPrint);
1654 }
1655 }
1656 throw ParseError(ss.str());
1657 }
1658
1659 // check constraints of sub arguments recursively
1660 checkConstraints(arg->m_subArgs);
1661 }
1662}
1663
1672{
1673 for (const Argument *arg : args) {
1674 // invoke the callback for each occurrence of the argument
1675 if (arg->m_callbackFunction) {
1676 for (const auto &occurrence : arg->m_occurrences) {
1677 arg->m_callbackFunction(occurrence);
1678 }
1679 }
1680 // invoke the callbacks for sub arguments recursively
1681 invokeCallbacks(arg->m_subArgs);
1682 }
1683}
1684
1688void ArgumentParser::invokeExit(int code)
1689{
1690 if (m_exitFunction) {
1691 m_exitFunction(code);
1692 return;
1693 }
1694 std::exit(code);
1695}
1696
1707 : Argument("help", 'h', "shows this information")
1708{
1709 setCallback([&parser](const ArgumentOccurrence &) {
1711 parser.printHelp(cout);
1712 });
1713}
1714
1750#ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1751 : Argument("no-color", '\0', "disables formatted/colorized output")
1752#else
1753 : Argument("enable-color", '\0', "enables formatted/colorized output")
1754#endif
1755{
1756 setCombinable(true);
1757
1758 // set the environment variable (not directly used and just assigned for printing help)
1759 setEnvironmentVariable("ENABLE_ESCAPE_CODES");
1760
1761 // initialize EscapeCodes::enabled from environment variable
1762 const auto escapeCodesEnabled = isEnvVariableSet(environmentVariable());
1763 if (escapeCodesEnabled.has_value()) {
1764 EscapeCodes::enabled = escapeCodesEnabled.value();
1765 }
1766}
1767
1772{
1773 if (isPresent()) {
1774#ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1775 EscapeCodes::enabled = false;
1776#else
1777 EscapeCodes::enabled = true;
1778#endif
1779 }
1780}
1781
1785void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(const std::vector<Argument *> &argumentPath) const
1786{
1787 throw ParseError(argumentPath.empty()
1788 ? argsToString("Conversion of top-level value \"", valueToConvert, "\" to type \"", targetTypeName, "\" failed: ", errorMessage)
1789 : argsToString("Conversion of value \"", valueToConvert, "\" (for argument --", argumentPath.back()->name(), ") to type \"",
1790 targetTypeName, "\" failed: ", errorMessage));
1791}
1792
1796void ArgumentOccurrence::throwNumberOfValuesNotSufficient(unsigned long valuesToConvert) const
1797{
1798 throw ParseError(path.empty()
1799 ? argsToString("Expected ", valuesToConvert, " top-level values to be present but only ", values.size(), " have been specified.")
1800 : argsToString("Expected ", valuesToConvert, " values for argument --", path.back()->name(), " to be present but only ", values.size(),
1801 " have been specified."));
1802}
1803
1804} // namespace CppUtilities
#define CPP_UTILITIES_IF_DEBUG_BUILD(x)
Wraps debug-only lines conveniently.
Definition: global.h:102
The ArgumentParser class provides a means for handling command line arguments.
void checkConstraints()
Checks whether constraints are violated.
ArgumentParser()
Constructs a new ArgumentParser.
void readArgs(int argc, const char *const *argv)
Parses the specified command line arguments.
const ArgumentVector & mainArguments() const
Returns the main arguments.
bool isUncombinableMainArgPresent() const
Checks whether at least one uncombinable main argument is present.
void setMainArguments(const ArgumentInitializerList &mainArguments)
Sets the main arguments for the parser.
void printHelp(std::ostream &os) const
Prints help text for all assigned arguments.
void addMainArgument(Argument *argument)
Adds the specified argument to the main argument.
void parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior=ParseArgumentBehavior::CheckConstraints|ParseArgumentBehavior::InvokeCallbacks|ParseArgumentBehavior::ExitOnFailure)
Parses the specified command line arguments.
void invokeCallbacks()
Invokes all assigned callbacks.
void resetArgs()
Resets all Argument instances assigned as mainArguments() and sub arguments.
Argument * specifiedOperation() const
Returns the first operation argument specified by the user or nullptr if no operation has been specif...
The ArgumentReader class internally encapsulates the process of reading command line arguments.
const char *const * lastArgDenotation
Points to the element in argv where lastArg was encountered. Unspecified if lastArg is not set.
const char * argDenotation
The currently processed abbreviation denotation (should be substring of one of the args in argv)....
ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode=false)
Initializes the internal reader for the specified parser and arguments.
ArgumentVector & args
The Argument instances to store the results. Sub arguments of args are considered as well.
const char *const * argv
Points to the first argument denotation and will be incremented when a denotation has been processed.
Argument * lastArg
The last Argument instance which could be detected. Set to nullptr in the initial call....
bool completionMode
Whether completion mode is enabled. In this case reading args will be continued even if an denotation...
unsigned char argDenotationType
The type of the currently processed abbreviation denotation. Unspecified if argDenotation is not set.
std::size_t index
An index which is incremented when an argument is encountered (the current index is stored in the occ...
ArgumentReader & reset(const char *const *argv, const char *const *end)
Resets the ArgumentReader to continue reading new argv.
ArgumentParser & parser
The associated ArgumentParser instance.
bool read()
Reads the commands line arguments specified when constructing the object.
const char *const * end
Points to the end of the argv array.
The Argument class is a wrapper for command line argument information.
const char * description() const
Returns the description of the argument.
Argument(const char *name, char abbreviation='\0', const char *description=nullptr, const char *example=nullptr)
Constructs an Argument with the given name, abbreviation and description.
bool isParentPresent() const
Returns whether at least one parent argument is present.
void addSubArgument(Argument *arg)
Adds arg as a secondary argument for this argument.
static constexpr std::size_t varValueCount
Denotes a variable number of values.
const std::vector< Argument * > & path(std::size_t occurrence=0) const
Returns the path of the specified occurrence.
bool isMainArgument() const
Returns an indication whether the argument is used as main argument.
const char * firstValue() const
Returns the first parameter value of the first occurrence of the argument.
const ArgumentVector & subArguments() const
Returns the secondary arguments for this argument.
const char * example() const
Returns the usage example of the argument.
Flags
The Flags enum specifies options for treating the argument in a special way.
const std::vector< const char * > & valueNames() const
Returns the names of the required values.
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 setCallback(CallbackFunction callback)
Sets a callback function which will be called by the parser if the argument could be found and no par...
bool isRequired() const
Returns an indication whether the argument is mandatory.
void printInfo(std::ostream &os, unsigned char indentation=0) const
Writes the name, the abbreviation and other information about the Argument to the give ostream.
const char * firstValueOr(const char *fallback) const
Returns the first value like Argument::firstValue() but returns fallback instead of nullptr if there'...
The ConversionException class is thrown by the various conversion functions of this library when a co...
HelpArgument(ArgumentParser &parser)
Constructs a new help argument for the specified parser.
The Indentation class allows printing indentation conveniently, eg.
void apply() const
Sets EscapeCodes::enabled according to the presence of the first instantiation of NoColorArgument.
NoColorArgument()
Constructs a new NoColorArgument argument.
The ParseError class is thrown by an ArgumentParser when a parsing error occurs.
Definition: parseerror.h:11
The Wrapper class is internally used print text which might needs to be wrapped preserving the indent...
#define CMD_UTILS_START_CONSOLE
#define CPP_UTILITIES_EXPORT
Marks the symbol to be exported by the c++utilities library.
Encapsulates functions for formatted terminal output using ANSI escape codes.
CPP_UTILITIES_EXPORT bool enabled
Controls whether the functions inside the EscapeCodes namespace actually make use of escape codes.
void setStyle(std::ostream &stream, TextAttribute displayAttribute=TextAttribute::Reset)
Contains all utilities provides by the c++utilities library.
void findAndReplace(StringType1 &str, const StringType2 &find, const StringType3 &replace)
Replaces all occurrences of find with relpace in the specified str.
CPP_UTILITIES_EXPORT TerminalSize determineTerminalSize()
Returns the current size of the terminal.
std::vector< Argument * > ArgumentVector
Argument * firstPresentUncombinableArg(const ArgumentVector &args, const Argument *except)
This function return the first present and uncombinable argument of the given list of arguments.
CPP_UTILITIES_EXPORT std::ostream & operator<<(std::ostream &out, Indentation indentation)
UnknownArgumentBehavior
The UnknownArgumentBehavior enum specifies the behavior of the argument parser when an unknown argume...
bool startsWith(const StringType &str, const StringType &phrase)
Returns whether str starts with phrase.
ParseArgumentBehavior
The ParseArgumentBehavior enum specifies the behavior when parsing arguments.
std::initializer_list< Argument * > ArgumentInitializerList
bool operator==(const AsHexNumber< T > &lhs, const AsHexNumber< T > &rhs)
Provides operator == required by CPPUNIT_ASSERT_EQUAL.
Definition: testutils.h:230
StringType argsToString(Args &&...args)
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.
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