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) {
380 case UnknownArgumentBehavior::Warn:
381 cerr << Phrases::Warning <<
"The specified argument \"" << *
argv <<
"\" is unknown and will be ignored." << Phrases::EndFlush;
383 case UnknownArgumentBehavior::Ignore:
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;
913 if (behavior & ParseArgumentBehavior::CheckConstraints) {
916 if (behavior & ParseArgumentBehavior::InvokeCallbacks) {
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) {
1162 if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) {
1163 completion.relevantPreDefinedValues.push_back(arg);
1165 if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues) || !arg->preDefinedCompletionValues()) {
1166 completion.completeFiles = completion.completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
1167 completion.completeDirs = completion.completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
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()) {
1362 const bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
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 parameter for argument \"" << arg->name() <<
"\" ";
1616 ss <<
" (" << (
i + 1) <<
" occurrence) ";
1618 ss <<
"provided. You have to provide the following parameter:";
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."));