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"
20 #ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
25 using namespace std::placeholders;
26 using namespace std::literals;
51 size_t lastDetectedArgIndex = 0;
55 const char *
const *lastSpecifiedArg =
nullptr;
56 unsigned int lastSpecifiedArgIndex = 0;
57 bool nextArgumentOrValue =
false;
58 bool completeFiles =
false, completeDirs =
false;
66 : lastDetectedArg(reader.lastArg)
71 struct 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;
78 const char *
const suggestion;
79 const size_t suggestionSize;
80 const size_t editingDistance;
81 const bool hasDashPrefix;
84 ArgumentSuggestion::ArgumentSuggestion(
const char *unknownArg,
size_t unknownArgSize,
const char *suggestion,
size_t suggestionSize,
bool isOperation)
85 : suggestion(suggestion)
86 , suggestionSize(suggestionSize)
88 , hasDashPrefix(isOperation)
92 ArgumentSuggestion::ArgumentSuggestion(
const char *unknownArg,
size_t unknownArgSize,
const char *suggestion,
bool isOperation)
93 : ArgumentSuggestion(unknownArg, unknownArgSize, suggestion, strlen(suggestion), isOperation)
97 bool ArgumentSuggestion::operator<(
const ArgumentSuggestion &other)
const
99 return editingDistance < other.editingDistance;
102 void ArgumentSuggestion::addTo(multiset<ArgumentSuggestion> &suggestions,
size_t limit)
const
104 if (suggestions.size() >= limit && !(*
this < *--suggestions.end())) {
107 suggestions.emplace(*
this);
108 while (suggestions.size() > limit) {
109 suggestions.erase(--suggestions.end());
128 , args(parser.m_mainArgs)
133 , argDenotation(nullptr)
134 , completionMode(completionMode)
163 bool Argument::matchesDenotation(
const char *denotation,
size_t denotationLength)
const
165 return m_name && !strncmp(m_name, denotation, denotationLength) && *(m_name + denotationLength) ==
'\0';
180 const vector<Argument *> &parentPath = parentArg ? parentArg->
path(parentArg->
occurrences() - 1) : vector<Argument *>();
183 vector<const char *> *values =
nullptr;
198 bool abbreviationFound =
false;
201 abbreviationFound =
false;
213 abbreviationFound =
false;
234 for (; argDenotationLength; matchingArg =
nullptr) {
238 if (arg->abbreviation() && arg->abbreviation() == *
argDenotation) {
240 abbreviationFound =
true;
246 if (arg->matchesDenotation(
argDenotation, argDenotationLength)) {
257 matchingArg->m_occurrences.emplace_back(
index, parentPath, parentArg);
260 values = &matchingArg->m_occurrences.back().values;
264 values->push_back(equationPos + 1);
271 lastArg = lastArgInLevel = matchingArg;
282 const char *
const *
const currentArgValue =
argv;
299 for (
auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend();; ++parentArgument) {
300 for (
Argument *
const sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() :
parser.m_mainArgs)) {
301 if (sibling->occurrences() < sibling->maxOccurrences()) {
307 if (sibling->matchesDenotation(
argDenotation, argDenotationLength)) {
312 if (parentArgument == pathEnd) {
329 if (arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *
argv)) {
330 (matchingArg = arg)->m_occurrences.emplace_back(
index, parentPath, parentArg);
342 if (arg->isImplicit() && !arg->isPresent() && !arg->wouldConflictWithArgument()
343 && (!uncombinableMainArgPresent || !arg->isMainArgument())) {
344 (matchingArg = arg)->m_occurrences.emplace_back(
index, parentPath, parentArg);
352 if (lastArgInLevel == matchingArg) {
357 values = &matchingArg->m_occurrences.back().values;
361 lastArg = lastArgInLevel = matchingArg;
379 switch (
parser.m_unknownArgBehavior) {
381 cerr << Phrases::Warning <<
"The specified argument \"" << *
argv <<
"\" is unknown and will be ignored." << Phrases::EndFlush;
410 unsigned short currentCol = wrapper.m_indentation.
level;
411 for (
const char *currentChar = wrapper.m_str; *currentChar; ++currentChar) {
412 const bool wrappingRequired = currentCol >= maxColumns;
413 if (wrappingRequired || *currentChar ==
'\n') {
417 if (wrapper.m_indentation.
level < maxColumns) {
418 os << wrapper.m_indentation;
419 currentCol = wrapper.m_indentation.
level;
424 if (*currentChar !=
'\n' && (!wrappingRequired || *currentChar !=
' ')) {
436 inline bool notEmpty(
const char *str)
459 Argument::Argument(
const char *name,
char abbreviation,
const char *description,
const char *example)
461 , m_abbreviation(abbreviation)
462 , m_environmentVar(nullptr)
463 , m_description(description)
465 , m_minOccurrences(0)
466 , m_maxOccurrences(1)
467 , m_requiredValueCount(0)
469 , m_deprecatedBy(nullptr)
473 , m_preDefinedCompletionValues(nullptr)
493 if (!m_occurrences.empty() && !m_occurrences.front().values.empty()) {
494 return m_occurrences.front().values.front();
495 }
else if (m_environmentVar) {
496 return getenv(m_environmentVar);
525 if (notEmpty(
name())) {
539 unsigned int valueNamesPrint = 0;
541 os <<
' ' <<
'[' << *
i <<
']';
548 os <<
" [value " << (valueNamesPrint + 1) <<
']';
557 os <<
'\n' << ident <<
"particularities: mandatory";
559 os <<
" if parent argument is present";
566 bool hasSubArgs =
false;
568 if (arg->isDeprecated()) {
572 arg->printInfo(os, ident.
level);
575 if (ident.
level == 2 && hasSubArgs) {
591 if (arg != except && arg->
isPresent() && !arg->isCombinable()) {
616 arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(),
this), arg->m_parents.end());
619 m_subArgs.assign(secondaryArguments);
623 if (find(arg->m_parents.cbegin(), arg->m_parents.cend(),
this) == arg->m_parents.cend()) {
624 arg->m_parents.push_back(
this);
638 if (find(m_subArgs.cbegin(), m_subArgs.cend(), arg) != m_subArgs.cend()) {
641 m_subArgs.push_back(arg);
642 if (find(arg->m_parents.cbegin(), arg->m_parents.cend(),
this) == arg->m_parents.cend()) {
643 arg->m_parents.push_back(
this);
656 for (
const Argument *parent : m_parents) {
657 if (parent->isPresent()) {
690 for (
Argument *parent : m_parents) {
692 if (sibling !=
this && sibling->isPresent() && !sibling->isCombinable()) {
707 if (arg->denotesOperation() && arg->isPresent()) {
721 arg->resetRecursively();
744 , m_executable(nullptr)
746 , m_defaultArg(nullptr)
767 arg->m_isMainArg =
true;
773 bool subArgsRequired =
false;
775 if (subArg->isRequired()) {
776 subArgsRequired =
true;
780 if (!subArgsRequired) {
793 argument->m_isMainArg =
true;
794 m_mainArgs.push_back(argument);
803 bool wroteLine =
false;
828 if (!m_mainArgs.empty()) {
829 bool hasOperations =
false, hasTopLevelOptions =
false;
830 for (
const Argument *
const arg : m_mainArgs) {
831 if (arg->denotesOperation()) {
832 hasOperations =
true;
833 }
else if (strcmp(arg->name(),
"help")) {
834 hasTopLevelOptions =
true;
836 if (hasOperations && hasTopLevelOptions) {
844 os <<
"Available operations:";
845 for (
const Argument *
const arg : m_mainArgs) {
846 if (!arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(),
"help")) {
852 if (hasTopLevelOptions) {
853 os <<
"\nAvailable top-level options:";
854 for (
const Argument *
const arg : m_mainArgs) {
855 if (arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(),
"help")) {
864 os <<
"Available arguments:";
865 for (
const Argument *
const arg : m_mainArgs) {
866 if (arg->isDeprecated() || !strcmp(arg->name(),
"help")) {
878 os <<
"Linked against: " << *
i;
879 for (++
i;
i != end; ++
i) {
880 os <<
',' <<
' ' << *
i;
949 m_executable =
nullptr;
952 m_executable = *argv;
958 m_defaultArg->m_occurrences.emplace_back(0);
964 const bool completionMode = !strcmp(*++argv,
"--bash-completion-for");
967 unsigned int currentWordIndex = 0, argcForReader;
968 if (completionMode) {
971 currentWordIndex = (--argc ? stringToNumber<unsigned int, string>(*(++argv)) : 0);
977 currentWordIndex =
static_cast<unsigned int>(argc - 1);
979 argcForReader =
min(
static_cast<unsigned int>(argc), currentWordIndex + 1);
981 argcForReader =
static_cast<unsigned int>(argc);
985 ArgumentReader reader(*
this, argv, argv + argcForReader, completionMode);
986 const bool allArgsProcessed(reader.
read());
987 m_noColorArg.
apply();
990 if (!completionMode && !allArgsProcessed) {
991 const auto suggestions(findSuggestions(argc, argv,
static_cast<unsigned int>(argc - 1), reader));
996 if (completionMode) {
997 printBashCompletion(argc, argv, currentWordIndex, reader);
1009 arg->resetRecursively();
1022 if (arg->denotesOperation() && arg->isPresent()) {
1034 for (
const Argument *arg : m_mainArgs) {
1035 if (!arg->isCombinable() && arg->isPresent()) {
1042 #ifdef CPP_UTILITIES_DEBUG_BUILD
1059 vector<const Argument *> verifiedArgs;
1060 verifiedArgs.reserve(args.size());
1061 vector<char> abbreviations;
1062 abbreviations.reserve(abbreviations.size() + args.size());
1063 vector<const char *> names;
1064 names.reserve(names.size() + args.size());
1065 bool hasImplicit =
false;
1067 assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
1068 verifiedArgs.push_back(arg);
1069 assert(!arg->isImplicit() || !hasImplicit);
1070 hasImplicit |= arg->isImplicit();
1071 assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
1072 abbreviations.push_back(arg->abbreviation());
1073 assert(!arg->name() || find_if(names.cbegin(), names.cend(), [arg](
const char *name) { return !strcmp(arg->name(), name); }) == names.cend());
1074 assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0);
1075 names.emplace_back(arg->name());
1077 for (
const Argument *arg : args) {
1078 verifyArgs(arg->subArguments());
1097 return strcmp(arg1->
name(), arg2->
name()) < 0;
1107 bool onlyCombinable =
false;
1108 for (
const Argument *sibling : siblings) {
1109 if (sibling->isPresent() && !sibling->isCombinable()) {
1110 onlyCombinable =
true;
1114 for (
const Argument *sibling : siblings) {
1115 if ((!onlyCombinable || sibling->isCombinable()) && sibling->occurrences() < sibling->maxOccurrences()) {
1116 target.push_back(sibling);
1124 ArgumentCompletionInfo ArgumentParser::determineCompletionInfo(
1125 int argc,
const char *
const *argv,
unsigned int currentWordIndex,
const ArgumentReader &reader)
const
1127 ArgumentCompletionInfo completion(reader);
1130 if (completion.lastDetectedArg) {
1131 completion.lastDetectedArgIndex =
static_cast<size_t>(reader.lastArgDenotation - argv);
1132 completion.lastDetectedArgPath = completion.lastDetectedArg->path(completion.lastDetectedArg->occurrences() - 1);
1137 completion.lastSpecifiedArgIndex =
static_cast<unsigned int>(argc) - 1;
1138 completion.lastSpecifiedArg = argv + completion.lastSpecifiedArgIndex;
1139 for (; completion.lastSpecifiedArg >= argv && **completion.lastSpecifiedArg ==
'\0';
1140 --completion.lastSpecifiedArg, --completion.lastSpecifiedArgIndex)
1145 if (!completion.lastDetectedArg || !completion.lastDetectedArg->isPresent()) {
1146 completion.nextArgumentOrValue =
true;
1152 completion.nextArgumentOrValue = currentWordIndex > completion.lastDetectedArgIndex;
1153 if (!completion.nextArgumentOrValue) {
1155 completion.relevantArgs.push_back(completion.lastDetectedArg);
1161 const auto addValueCompletionsForArg = [&completion](
const Argument *arg) {
1163 completion.relevantPreDefinedValues.push_back(arg);
1172 auto currentValueCount = completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size();
1174 if (currentValueCount) {
1175 const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - completion.lastDetectedArgIndex;
1176 if (currentValueCount > currentWordIndexRelativeToLastDetectedArg) {
1177 currentValueCount -= currentWordIndexRelativeToLastDetectedArg;
1179 currentValueCount = 0;
1185 if (!currentValueCount && !completion.lastDetectedArg->requiredValueCount()) {
1186 for (
const Argument *child : completion.lastDetectedArg->subArguments()) {
1187 if (child->isImplicit() && child->requiredValueCount()) {
1188 addValueCompletionsForArg(child);
1196 || (currentValueCount < completion.lastDetectedArg->requiredValueCount())) {
1197 addValueCompletionsForArg(completion.lastDetectedArg);
1201 || completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size()
1202 >= completion.lastDetectedArg->requiredValueCount()) {
1204 for (
const Argument *subArg : completion.lastDetectedArg->subArguments()) {
1205 if (subArg->occurrences() < subArg->maxOccurrences()) {
1206 completion.relevantArgs.push_back(subArg);
1211 for (
auto parentArgument = completion.lastDetectedArgPath.crbegin(), end = completion.lastDetectedArgPath.crend();; ++parentArgument) {
1212 insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, completion.relevantArgs);
1213 if (parentArgument == end) {
1225 string ArgumentParser::findSuggestions(
int argc,
const char *
const *argv,
unsigned int cursorPos,
const ArgumentReader &reader)
const
1228 const auto completionInfo(determineCompletionInfo(argc, argv, cursorPos, reader));
1231 const auto *unknownArg(*reader.argv);
1232 auto unknownArgSize(strlen(unknownArg));
1234 if (unknownArgSize > 16) {
1238 if (unknownArgSize >= 2 && unknownArg[0] ==
'-' && unknownArg[1] ==
'-') {
1240 unknownArgSize -= 2;
1244 multiset<ArgumentSuggestion> bestSuggestions;
1246 for (
const Argument *
const arg : completionInfo.relevantArgs) {
1247 ArgumentSuggestion(unknownArg, unknownArgSize, arg->name(), !arg->denotesOperation()).addTo(bestSuggestions, 2);
1250 for (
const Argument *
const arg : completionInfo.relevantPreDefinedValues) {
1251 if (!arg->preDefinedCompletionValues()) {
1254 for (
const char *
i = arg->preDefinedCompletionValues(); *
i; ++
i) {
1255 const char *
const wordStart(
i);
1256 const char *wordEnd(wordStart + 1);
1257 for (; *wordEnd && *wordEnd !=
' '; ++wordEnd)
1259 ArgumentSuggestion(unknownArg, unknownArgSize, wordStart,
static_cast<size_t>(wordEnd - wordStart),
false).addTo(bestSuggestions, 2);
1265 string suggestionStr;
1266 if (
const auto suggestionCount = bestSuggestions.size()) {
1268 size_t requiredSize = 15;
1269 for (
const auto &suggestion : bestSuggestions) {
1270 requiredSize += suggestion.suggestionSize + 2;
1271 if (suggestion.hasDashPrefix) {
1275 suggestionStr.reserve(requiredSize);
1278 suggestionStr +=
"\nDid you mean ";
1280 for (
const auto &suggestion : bestSuggestions) {
1281 if (++
i == suggestionCount && suggestionCount != 1) {
1282 suggestionStr +=
" or ";
1284 suggestionStr +=
", ";
1286 if (suggestion.hasDashPrefix) {
1287 suggestionStr +=
"--";
1289 suggestionStr.append(suggestion.suggestion, suggestion.suggestionSize);
1291 suggestionStr +=
'?';
1293 return suggestionStr;
1301 void ArgumentParser::printBashCompletion(
int argc,
const char *
const *argv,
unsigned int currentWordIndex,
const ArgumentReader &reader)
const
1304 const auto completionInfo([&] {
1305 auto clutteredCompletionInfo(determineCompletionInfo(argc, argv, currentWordIndex, reader));
1306 clutteredCompletionInfo.relevantArgs.sort(
compareArgs);
1307 return clutteredCompletionInfo;
1311 const char *opening =
nullptr;
1312 string compoundOpening;
1313 size_t openingLen = 0, compoundOpeningStartLen = 0;
1314 unsigned char openingDenotationType =
Value;
1315 if (argc && completionInfo.nextArgumentOrValue) {
1316 if (currentWordIndex <
static_cast<unsigned int>(argc)) {
1317 opening = argv[currentWordIndex];
1323 const size_t minCurrentWordIndex = (completionInfo.lastDetectedArg ? completionInfo.lastDetectedArgIndex : 0);
1324 if (currentWordIndex > minCurrentWordIndex && !strcmp(opening,
"=")) {
1325 compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
1326 compoundOpening = argv[currentWordIndex];
1327 compoundOpening +=
'=';
1328 }
else if (currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1],
"=")) {
1329 compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
1330 compoundOpening = argv[currentWordIndex];
1331 compoundOpening +=
'=';
1332 compoundOpening += opening;
1334 if (!compoundOpening.empty()) {
1335 opening = compoundOpening.data();
1338 opening = *completionInfo.lastSpecifiedArg;
1340 if (*opening ==
'-') {
1342 ++openingDenotationType;
1343 if (*opening ==
'-') {
1345 ++openingDenotationType;
1348 openingLen = strlen(opening);
1352 cout <<
"COMPREPLY=(";
1354 bool noWhitespace =
false;
1355 for (
const Argument *
const arg : completionInfo.relevantPreDefinedValues) {
1357 arg->m_callbackFunction(arg->isPresent() ? arg->m_occurrences.front() : ArgumentOccurrence(
Argument::varValueCount));
1359 if (!arg->preDefinedCompletionValues()) {
1363 if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1364 if (openingDenotationType !=
Value) {
1367 bool wordStart =
true, ok =
false, equationSignAlreadyPresent =
false;
1368 size_t wordIndex = 0;
1369 for (
const char *
i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *
i;) {
1371 const char *i1 =
i, *i2 = opening;
1372 for (; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2)
1374 if ((ok = (i2 == end))) {
1379 }
else if ((wordStart = (*
i ==
' ') || (*
i ==
'\n'))) {
1380 equationSignAlreadyPresent =
false;
1382 cout <<
'\'' <<
' ';
1386 }
else if (*
i ==
'=') {
1387 equationSignAlreadyPresent =
true;
1393 if (!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
1406 if (appendEquationSign && !equationSignAlreadyPresent) {
1408 noWhitespace =
true;
1409 equationSignAlreadyPresent =
false;
1417 }
else if (
const char *
i = arg->preDefinedCompletionValues()) {
1418 bool equationSignAlreadyPresent =
false;
1428 equationSignAlreadyPresent =
true;
1433 if (appendEquationSign && !equationSignAlreadyPresent) {
1435 equationSignAlreadyPresent =
false;
1440 cout <<
' ' <<
'\'';
1445 cout <<
'\'' <<
' ';
1449 for (
const Argument *
const arg : completionInfo.relevantArgs) {
1450 if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1451 switch (openingDenotationType) {
1453 if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1460 if (strncmp(arg->name(), opening, openingLen)) {
1466 if (opening && openingDenotationType ==
Abbreviation && !completionInfo.nextArgumentOrValue) {
1468 cout <<
'\'' <<
'-' << opening << arg->abbreviation() <<
'\'' <<
' ';
1469 }
else if (completionInfo.lastDetectedArg && reader.argDenotationType ==
Abbreviation && !completionInfo.nextArgumentOrValue) {
1470 if (reader.argv == reader.end) {
1471 cout <<
'\'' << *(reader.argv - 1) <<
'\'' <<
' ';
1473 }
else if (arg->denotesOperation()) {
1474 cout <<
'\'' << arg->name() <<
'\'' <<
' ';
1476 cout <<
'\'' <<
'-' <<
'-' << arg->name() <<
'\'' <<
' ';
1481 string actualDir, actualFile;
1482 bool haveFileOrDirCompletions =
false;
1483 if (argc && currentWordIndex == completionInfo.lastSpecifiedArgIndex && opening) {
1485 string unescapedOpening(opening);
1486 findAndReplace<string>(unescapedOpening,
"\\ ",
" ");
1487 findAndReplace<string>(unescapedOpening,
"\\,",
",");
1488 findAndReplace<string>(unescapedOpening,
"\\[",
"[");
1489 findAndReplace<string>(unescapedOpening,
"\\]",
"]");
1490 findAndReplace<string>(unescapedOpening,
"\\!",
"!");
1491 findAndReplace<string>(unescapedOpening,
"\\#",
"#");
1492 findAndReplace<string>(unescapedOpening,
"\\$",
"$");
1493 findAndReplace<string>(unescapedOpening,
"\\'",
"'");
1494 findAndReplace<string>(unescapedOpening,
"\\\"",
"\"");
1495 findAndReplace<string>(unescapedOpening,
"\\\\",
"\\");
1497 string dir =
directory(unescapedOpening);
1501 if (dir[0] ==
'\"' || dir[0] ==
'\'') {
1504 if (dir.size() > 1 && (dir[dir.size() - 2] ==
'\"' || dir[dir.size() - 2] ==
'\'')) {
1505 dir.erase(dir.size() - 2, 1);
1507 actualDir = move(dir);
1510 string file =
fileName(unescapedOpening);
1511 if (file[0] ==
'\"' || file[0] ==
'\'') {
1514 if (file.size() > 1 && (file[dir.size() - 2] ==
'\"' || dir[file.size() - 2] ==
'\'')) {
1515 file.erase(file.size() - 2, 1);
1517 actualFile = move(file);
1521 #ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
1522 if (completionInfo.completeFiles || completionInfo.completeDirs) {
1524 const auto replace =
"'"s, with =
"'\"'\"'"s;
1525 const auto useActualDir = argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening;
1526 const auto dirEntries = [&] {
1527 filesystem::directory_iterator
i;
1529 i = filesystem::directory_iterator(actualDir);
1532 i = filesystem::directory_iterator(
".");
1536 for (
const auto &dirEntry : dirEntries) {
1537 if (!completionInfo.completeDirs && dirEntry.is_directory()) {
1540 if (!completionInfo.completeFiles && !dirEntry.is_directory()) {
1543 auto dirEntryName = dirEntry.path().filename().string();
1544 auto hasStartingQuote =
false;
1550 hasStartingQuote =
true;
1551 if (actualDir !=
".") {
1556 if (!hasStartingQuote) {
1559 cout << dirEntryName <<
'\'' <<
' ';
1560 haveFileOrDirCompletions =
true;
1562 }
catch (
const filesystem::filesystem_error &) {
1570 if (haveFileOrDirCompletions) {
1571 cout <<
"; compopt -o filenames";
1576 cout <<
"; compopt -o nospace";
1588 for (
const Argument *arg : args) {
1589 const auto occurrences = arg->occurrences();
1590 if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1591 throw ParseError(
argsToString(
"The argument \"", arg->name(),
"\" mustn't be specified more than ", arg->maxOccurrences(),
1592 (arg->maxOccurrences() == 1 ?
" time." :
" times.")));
1594 if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1595 throw ParseError(
argsToString(
"The argument \"", arg->name(),
"\" must be specified at least ", arg->minOccurrences(),
1596 (arg->minOccurrences() == 1 ?
" time." :
" times.")));
1598 Argument *conflictingArgument =
nullptr;
1599 if (arg->isMainArgument()) {
1600 if (!arg->isCombinable() && arg->isPresent()) {
1604 conflictingArgument = arg->conflictsWithArgument();
1606 if (conflictingArgument) {
1607 throw ParseError(
argsToString(
"The argument \"", conflictingArgument->name(),
"\" can not be combined with \"", arg->name(),
"\"."));
1609 for (
size_t i = 0;
i != occurrences; ++
i) {
1610 if (arg->allRequiredValuesPresent(
i)) {
1613 stringstream ss(stringstream::in | stringstream::out);
1614 ss <<
"Not all parameters for argument \"" << arg->name() <<
"\" ";
1616 ss <<
" (" << (
i + 1) <<
" occurrence) ";
1618 ss <<
"provided. You have to provide the following parameters:";
1619 size_t valueNamesPrint = 0;
1620 for (
const auto &name : arg->m_valueNames) {
1625 while (valueNamesPrint < arg->m_requiredValueCount) {
1626 ss <<
"\nvalue " << (++valueNamesPrint);
1629 throw ParseError(ss.str());
1646 for (
const Argument *arg : args) {
1648 if (arg->m_callbackFunction) {
1649 for (
const auto &occurrence : arg->m_occurrences) {
1650 arg->m_callbackFunction(occurrence);
1661 void ArgumentParser::invokeExit(
int code)
1663 if (m_exitFunction) {
1664 m_exitFunction(code);
1680 :
Argument(
"help",
'h',
"shows this information")
1723 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1724 :
Argument(
"no-color",
'\0',
"disables formatted/colorized output")
1726 :
Argument(
"enable-color",
'\0',
"enables formatted/colorized output")
1729 setCombinable(
true);
1732 setEnvironmentVariable(
"ENABLE_ESCAPE_CODES");
1735 const char *envValue = getenv(environmentVariable());
1739 for (; *envValue; ++envValue) {
1740 switch (*envValue) {
1760 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1771 void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(
const std::vector<Argument *> &argumentPath)
const
1774 ?
argsToString(
"Conversion of top-level value \"", valueToConvert,
"\" to type \"", targetTypeName,
"\" failed: ", errorMessage)
1775 :
argsToString(
"Conversion of value \"", valueToConvert,
"\" (for argument --", argumentPath.back()->name(),
") to type \"",
1776 targetTypeName,
"\" failed: ", errorMessage));
1782 void ArgumentOccurrence::throwNumberOfValuesNotSufficient(
unsigned long valuesToConvert)
const
1784 throw ParseError(
path.empty()
1785 ?
argsToString(
"Expected ", valuesToConvert,
" top-level values to be present but only ",
values.size(),
" have been specified.")
1786 :
argsToString(
"Expected ", valuesToConvert,
" values for argument --",
path.back()->name(),
" to be present but only ",
values.size(),
1787 " have been specified."));
#define CPP_UTILITIES_IF_DEBUG_BUILD(x)
Wraps debug-only lines conveniently.
The ArgumentParser class provides a means for handling command line arguments.
void checkConstraints()
Checks whether contraints 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.
bool isDeprecated() const
const std::vector< const char * > & valueNames() const
Returns the names of the requried values.
char abbreviation() const
Returns the abbreviation of the argument.
std::size_t occurrences() const
Returns how often the argument could be detected when parsing.
Argument * specifiedOperation() const
Returns the first operation argument specified by the user or nullptr if no operation has been specif...
const char * name() const
Returns the name of the argument.
bool isCombinable() const
Returns an indication whether the argument is combinable.
bool denotesOperation() const
Returns whether the argument denotes an operation.
void resetRecursively()
Resets this argument and all sub arguments recursively.
std::size_t requiredValueCount() const
Returns the number of values which are required to be given for this argument.
const char * environmentVariable() const
Returns the environment variable queried when firstValue() is called.
Argument * wouldConflictWithArgument() const
Checks if this argument would conflict with other arguments if it was present.
void reset()
Resets occurrences (indices, values and paths).
~Argument()
Destroys the Argument.
bool isPresent() const
Returns an indication whether the argument could be detected when parsing.
Argument * conflictsWithArgument() const
Checks if this arguments conflicts with other arguments.
void setSubArguments(const ArgumentInitializerList &subArguments)
Sets the secondary arguments for this arguments.
void setCallback(CallbackFunction callback)
Sets a callback function which will be called by the parser if the argument could be found and no par...
bool isRequired() const
Returns an indication whether the argument is mandatory.
void printInfo(std::ostream &os, unsigned char indentation=0) const
Writes the name, the abbreviation and other information about the Argument to the give ostream.
const char * firstValueOr(const char *fallback) const
Returns the first value like Argument::firstValue() but returns fallback instead of nullptr if there'...
The ConversionException class is thrown by the various conversion functions of this library when a co...
HelpArgument(ArgumentParser &parser)
Constructs a new help argument for the specified parser.
The Indentation class allows printing indentation conveniently, eg.
void apply() const
Sets EscapeCodes::enabled according to the presense 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.
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 occurences 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.
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
CPP_UTILITIES_EXPORT std::ostream & operator<<(std::ostream &out, Indentation indentation)
bool operator==(const AsHexNumber< T > &lhs, const AsHexNumber< T > &rhs)
Provides operator == required by CPPUNIT_ASSERT_EQUAL.
CPP_UTILITIES_EXPORT std::string directory(const std::string &path)
Returns the directory of the specified path string (including trailing slash).
StringType argsToString(Args &&...args)
CPP_UTILITIES_EXPORT std::string fileName(const std::string &path)
Returns the file name and extension of the specified path string.
constexpr T max(T first, T second)
Returns the greatest of the given items.
ValueCompletionBehavior
The ValueCompletionBehavior enum specifies the items to be considered when generating completion for ...
@ FileSystemIfNoPreDefinedValues
ArgumentDenotationType
The ArgumentDenotationType enum specifies the type of a given argument denotation.
constexpr T min(T first, T second)
Returns the smallest of the given items.
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.
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
const Argument *const lastDetectedArg
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