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);
513 if (notEmpty(
name())) {
527 unsigned int valueNamesPrint = 0;
529 os <<
' ' <<
'[' << *
i <<
']';
536 os <<
" [value " << (valueNamesPrint + 1) <<
']';
545 os <<
'\n' << ident <<
"particularities: mandatory";
547 os <<
" if parent argument is present";
554 bool hasSubArgs =
false;
556 if (arg->isDeprecated()) {
560 arg->printInfo(os, ident.level);
563 if (ident.level == 2 && hasSubArgs) {
579 if (arg != except && arg->
isPresent() && !arg->isCombinable()) {
604 arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(),
this), arg->m_parents.end());
607 m_subArgs.assign(secondaryArguments);
611 if (find(arg->m_parents.cbegin(), arg->m_parents.cend(),
this) == arg->m_parents.cend()) {
612 arg->m_parents.push_back(
this);
626 if (find(m_subArgs.cbegin(), m_subArgs.cend(), arg) != m_subArgs.cend()) {
629 m_subArgs.push_back(arg);
630 if (find(arg->m_parents.cbegin(), arg->m_parents.cend(),
this) == arg->m_parents.cend()) {
631 arg->m_parents.push_back(
this);
644 for (
const Argument *parent : m_parents) {
645 if (parent->isPresent()) {
678 for (
Argument *parent : m_parents) {
680 if (sibling !=
this && sibling->isPresent() && !sibling->isCombinable()) {
695 if (arg->denotesOperation() && arg->isPresent()) {
709 arg->resetRecursively();
732 , m_executable(nullptr)
734 , m_defaultArg(nullptr)
755 arg->m_isMainArg =
true;
761 bool subArgsRequired =
false;
763 if (subArg->isRequired()) {
764 subArgsRequired =
true;
768 if (!subArgsRequired) {
781 argument->m_isMainArg =
true;
782 m_mainArgs.push_back(argument);
791 bool wroteLine =
false;
816 if (!m_mainArgs.empty()) {
817 bool hasOperations =
false, hasTopLevelOptions =
false;
818 for (
const Argument *
const arg : m_mainArgs) {
819 if (arg->denotesOperation()) {
820 hasOperations =
true;
821 }
else if (strcmp(arg->name(),
"help")) {
822 hasTopLevelOptions =
true;
824 if (hasOperations && hasTopLevelOptions) {
832 os <<
"Available operations:";
833 for (
const Argument *
const arg : m_mainArgs) {
834 if (!arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(),
"help")) {
840 if (hasTopLevelOptions) {
841 os <<
"\nAvailable top-level options:";
842 for (
const Argument *
const arg : m_mainArgs) {
843 if (arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(),
"help")) {
852 os <<
"Available arguments:";
853 for (
const Argument *
const arg : m_mainArgs) {
854 if (arg->isDeprecated() || !strcmp(arg->name(),
"help")) {
866 os <<
"Linked against: " << *
i;
867 for (++
i;
i != end; ++
i) {
868 os <<
',' <<
' ' << *
i;
901 if (behavior & ParseArgumentBehavior::CheckConstraints) {
904 if (behavior & ParseArgumentBehavior::InvokeCallbacks) {
937 m_executable =
nullptr;
940 m_executable = *argv;
946 m_defaultArg->m_occurrences.emplace_back(0);
952 const bool completionMode = !strcmp(*++argv,
"--bash-completion-for");
955 unsigned int currentWordIndex = 0, argcForReader;
956 if (completionMode) {
959 currentWordIndex = (--argc ? stringToNumber<unsigned int, string>(*(++argv)) : 0);
965 currentWordIndex =
static_cast<unsigned int>(argc - 1);
967 argcForReader =
min(
static_cast<unsigned int>(argc), currentWordIndex + 1);
969 argcForReader =
static_cast<unsigned int>(argc);
973 ArgumentReader reader(*
this, argv, argv + argcForReader, completionMode);
974 const bool allArgsProcessed(reader.
read());
975 m_noColorArg.
apply();
978 if (!completionMode && !allArgsProcessed) {
979 const auto suggestions(findSuggestions(argc, argv,
static_cast<unsigned int>(argc - 1), reader));
984 if (completionMode) {
985 printBashCompletion(argc, argv, currentWordIndex, reader);
997 arg->resetRecursively();
1010 if (arg->denotesOperation() && arg->isPresent()) {
1022 for (
const Argument *arg : m_mainArgs) {
1023 if (!arg->isCombinable() && arg->isPresent()) {
1030 #ifdef CPP_UTILITIES_DEBUG_BUILD
1047 vector<const Argument *> verifiedArgs;
1048 verifiedArgs.reserve(args.size());
1049 vector<char> abbreviations;
1050 abbreviations.reserve(abbreviations.size() + args.size());
1051 vector<const char *> names;
1052 names.reserve(names.size() + args.size());
1053 bool hasImplicit =
false;
1055 assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
1056 verifiedArgs.push_back(arg);
1057 assert(!arg->isImplicit() || !hasImplicit);
1058 hasImplicit |= arg->isImplicit();
1059 assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
1060 abbreviations.push_back(arg->abbreviation());
1061 assert(!arg->name() || find_if(names.cbegin(), names.cend(), [arg](
const char *name) { return !strcmp(arg->name(), name); }) == names.cend());
1062 assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0);
1063 names.emplace_back(arg->name());
1065 for (
const Argument *arg : args) {
1066 verifyArgs(arg->subArguments());
1085 return strcmp(arg1->
name(), arg2->
name()) < 0;
1095 bool onlyCombinable =
false;
1096 for (
const Argument *sibling : siblings) {
1097 if (sibling->isPresent() && !sibling->isCombinable()) {
1098 onlyCombinable =
true;
1102 for (
const Argument *sibling : siblings) {
1103 if ((!onlyCombinable || sibling->isCombinable()) && sibling->occurrences() < sibling->maxOccurrences()) {
1104 target.push_back(sibling);
1112 ArgumentCompletionInfo ArgumentParser::determineCompletionInfo(
1113 int argc,
const char *
const *argv,
unsigned int currentWordIndex,
const ArgumentReader &reader)
const
1115 ArgumentCompletionInfo completion(reader);
1118 if (completion.lastDetectedArg) {
1119 completion.lastDetectedArgIndex =
static_cast<size_t>(reader.lastArgDenotation - argv);
1120 completion.lastDetectedArgPath = completion.lastDetectedArg->path(completion.lastDetectedArg->occurrences() - 1);
1125 completion.lastSpecifiedArgIndex =
static_cast<unsigned int>(argc) - 1;
1126 completion.lastSpecifiedArg = argv + completion.lastSpecifiedArgIndex;
1127 for (; completion.lastSpecifiedArg >= argv && **completion.lastSpecifiedArg ==
'\0';
1128 --completion.lastSpecifiedArg, --completion.lastSpecifiedArgIndex)
1133 if (!completion.lastDetectedArg || !completion.lastDetectedArg->isPresent()) {
1134 completion.nextArgumentOrValue =
true;
1140 completion.nextArgumentOrValue = currentWordIndex > completion.lastDetectedArgIndex;
1141 if (!completion.nextArgumentOrValue) {
1143 completion.relevantArgs.push_back(completion.lastDetectedArg);
1149 const auto addValueCompletionsForArg = [&completion](
const Argument *arg) {
1150 if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) {
1151 completion.relevantPreDefinedValues.push_back(arg);
1153 if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues) || !arg->preDefinedCompletionValues()) {
1154 completion.completeFiles = completion.completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
1155 completion.completeDirs = completion.completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
1160 auto currentValueCount = completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size();
1162 if (currentValueCount) {
1163 const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - completion.lastDetectedArgIndex;
1164 if (currentValueCount > currentWordIndexRelativeToLastDetectedArg) {
1165 currentValueCount -= currentWordIndexRelativeToLastDetectedArg;
1167 currentValueCount = 0;
1173 if (!currentValueCount && !completion.lastDetectedArg->requiredValueCount()) {
1174 for (
const Argument *child : completion.lastDetectedArg->subArguments()) {
1175 if (child->isImplicit() && child->requiredValueCount()) {
1176 addValueCompletionsForArg(child);
1184 || (currentValueCount < completion.lastDetectedArg->requiredValueCount())) {
1185 addValueCompletionsForArg(completion.lastDetectedArg);
1189 || completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size()
1190 >= completion.lastDetectedArg->requiredValueCount()) {
1192 for (
const Argument *subArg : completion.lastDetectedArg->subArguments()) {
1193 if (subArg->occurrences() < subArg->maxOccurrences()) {
1194 completion.relevantArgs.push_back(subArg);
1199 for (
auto parentArgument = completion.lastDetectedArgPath.crbegin(), end = completion.lastDetectedArgPath.crend();; ++parentArgument) {
1200 insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, completion.relevantArgs);
1201 if (parentArgument == end) {
1213 string ArgumentParser::findSuggestions(
int argc,
const char *
const *argv,
unsigned int cursorPos,
const ArgumentReader &reader)
const
1216 const auto completionInfo(determineCompletionInfo(argc, argv, cursorPos, reader));
1219 const auto *unknownArg(*reader.argv);
1220 auto unknownArgSize(strlen(unknownArg));
1222 if (unknownArgSize > 16) {
1226 if (unknownArgSize >= 2 && unknownArg[0] ==
'-' && unknownArg[1] ==
'-') {
1228 unknownArgSize -= 2;
1232 multiset<ArgumentSuggestion> bestSuggestions;
1234 for (
const Argument *
const arg : completionInfo.relevantArgs) {
1235 ArgumentSuggestion(unknownArg, unknownArgSize, arg->name(), !arg->denotesOperation()).addTo(bestSuggestions, 2);
1238 for (
const Argument *
const arg : completionInfo.relevantPreDefinedValues) {
1239 if (!arg->preDefinedCompletionValues()) {
1242 for (
const char *
i = arg->preDefinedCompletionValues(); *
i; ++
i) {
1243 const char *
const wordStart(
i);
1244 const char *wordEnd(wordStart + 1);
1245 for (; *wordEnd && *wordEnd !=
' '; ++wordEnd)
1247 ArgumentSuggestion(unknownArg, unknownArgSize, wordStart,
static_cast<size_t>(wordEnd - wordStart),
false).addTo(bestSuggestions, 2);
1253 string suggestionStr;
1254 if (
const auto suggestionCount = bestSuggestions.size()) {
1256 size_t requiredSize = 15;
1257 for (
const auto &suggestion : bestSuggestions) {
1258 requiredSize += suggestion.suggestionSize + 2;
1259 if (suggestion.hasDashPrefix) {
1263 suggestionStr.reserve(requiredSize);
1266 suggestionStr +=
"\nDid you mean ";
1268 for (
const auto &suggestion : bestSuggestions) {
1269 if (++
i == suggestionCount && suggestionCount != 1) {
1270 suggestionStr +=
" or ";
1272 suggestionStr +=
", ";
1274 if (suggestion.hasDashPrefix) {
1275 suggestionStr +=
"--";
1277 suggestionStr.append(suggestion.suggestion, suggestion.suggestionSize);
1279 suggestionStr +=
'?';
1281 return suggestionStr;
1289 void ArgumentParser::printBashCompletion(
int argc,
const char *
const *argv,
unsigned int currentWordIndex,
const ArgumentReader &reader)
const
1292 const auto completionInfo([&] {
1293 auto clutteredCompletionInfo(determineCompletionInfo(argc, argv, currentWordIndex, reader));
1294 clutteredCompletionInfo.relevantArgs.sort(
compareArgs);
1295 return clutteredCompletionInfo;
1299 const char *opening =
nullptr;
1300 string compoundOpening;
1301 size_t openingLen = 0, compoundOpeningStartLen = 0;
1302 unsigned char openingDenotationType =
Value;
1303 if (argc && completionInfo.nextArgumentOrValue) {
1304 if (currentWordIndex <
static_cast<unsigned int>(argc)) {
1305 opening = argv[currentWordIndex];
1311 const size_t minCurrentWordIndex = (completionInfo.lastDetectedArg ? completionInfo.lastDetectedArgIndex : 0);
1312 if (currentWordIndex > minCurrentWordIndex && !strcmp(opening,
"=")) {
1313 compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
1314 compoundOpening = argv[currentWordIndex];
1315 compoundOpening +=
'=';
1316 }
else if (currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1],
"=")) {
1317 compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
1318 compoundOpening = argv[currentWordIndex];
1319 compoundOpening +=
'=';
1320 compoundOpening += opening;
1322 if (!compoundOpening.empty()) {
1323 opening = compoundOpening.data();
1326 opening = *completionInfo.lastSpecifiedArg;
1328 if (*opening ==
'-') {
1330 ++openingDenotationType;
1331 if (*opening ==
'-') {
1333 ++openingDenotationType;
1336 openingLen = strlen(opening);
1340 cout <<
"COMPREPLY=(";
1342 bool noWhitespace =
false;
1343 for (
const Argument *
const arg : completionInfo.relevantPreDefinedValues) {
1345 arg->m_callbackFunction(arg->isPresent() ? arg->m_occurrences.front() : ArgumentOccurrence(
Argument::varValueCount));
1347 if (!arg->preDefinedCompletionValues()) {
1350 const bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
1351 if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1352 if (openingDenotationType !=
Value) {
1355 bool wordStart =
true, ok =
false, equationSignAlreadyPresent =
false;
1356 size_t wordIndex = 0;
1357 for (
const char *
i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *
i;) {
1359 const char *i1 =
i, *i2 = opening;
1360 for (; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2)
1362 if ((ok = (i2 == end))) {
1367 }
else if ((wordStart = (*
i ==
' ') || (*
i ==
'\n'))) {
1368 equationSignAlreadyPresent =
false;
1370 cout <<
'\'' <<
' ';
1374 }
else if (*
i ==
'=') {
1375 equationSignAlreadyPresent =
true;
1381 if (!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
1394 if (appendEquationSign && !equationSignAlreadyPresent) {
1396 noWhitespace =
true;
1397 equationSignAlreadyPresent =
false;
1405 }
else if (
const char *
i = arg->preDefinedCompletionValues()) {
1406 bool equationSignAlreadyPresent =
false;
1416 equationSignAlreadyPresent =
true;
1421 if (appendEquationSign && !equationSignAlreadyPresent) {
1423 equationSignAlreadyPresent =
false;
1428 cout <<
' ' <<
'\'';
1433 cout <<
'\'' <<
' ';
1437 for (
const Argument *
const arg : completionInfo.relevantArgs) {
1438 if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1439 switch (openingDenotationType) {
1441 if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1448 if (strncmp(arg->name(), opening, openingLen)) {
1454 if (opening && openingDenotationType ==
Abbreviation && !completionInfo.nextArgumentOrValue) {
1456 cout <<
'\'' <<
'-' << opening << arg->abbreviation() <<
'\'' <<
' ';
1457 }
else if (completionInfo.lastDetectedArg && reader.argDenotationType ==
Abbreviation && !completionInfo.nextArgumentOrValue) {
1458 if (reader.argv == reader.end) {
1459 cout <<
'\'' << *(reader.argv - 1) <<
'\'' <<
' ';
1461 }
else if (arg->denotesOperation()) {
1462 cout <<
'\'' << arg->name() <<
'\'' <<
' ';
1464 cout <<
'\'' <<
'-' <<
'-' << arg->name() <<
'\'' <<
' ';
1469 string actualDir, actualFile;
1470 bool haveFileOrDirCompletions =
false;
1471 if (argc && currentWordIndex == completionInfo.lastSpecifiedArgIndex && opening) {
1473 string unescapedOpening(opening);
1474 findAndReplace<string>(unescapedOpening,
"\\ ",
" ");
1475 findAndReplace<string>(unescapedOpening,
"\\,",
",");
1476 findAndReplace<string>(unescapedOpening,
"\\[",
"[");
1477 findAndReplace<string>(unescapedOpening,
"\\]",
"]");
1478 findAndReplace<string>(unescapedOpening,
"\\!",
"!");
1479 findAndReplace<string>(unescapedOpening,
"\\#",
"#");
1480 findAndReplace<string>(unescapedOpening,
"\\$",
"$");
1481 findAndReplace<string>(unescapedOpening,
"\\'",
"'");
1482 findAndReplace<string>(unescapedOpening,
"\\\"",
"\"");
1483 findAndReplace<string>(unescapedOpening,
"\\\\",
"\\");
1485 string dir =
directory(unescapedOpening);
1489 if (dir[0] ==
'\"' || dir[0] ==
'\'') {
1492 if (dir.size() > 1 && (dir[dir.size() - 2] ==
'\"' || dir[dir.size() - 2] ==
'\'')) {
1493 dir.erase(dir.size() - 2, 1);
1495 actualDir = move(dir);
1498 string file =
fileName(unescapedOpening);
1499 if (file[0] ==
'\"' || file[0] ==
'\'') {
1502 if (file.size() > 1 && (file[dir.size() - 2] ==
'\"' || dir[file.size() - 2] ==
'\'')) {
1503 file.erase(file.size() - 2, 1);
1505 actualFile = move(file);
1509 #ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
1510 if (completionInfo.completeFiles || completionInfo.completeDirs) {
1512 const auto replace =
"'"s, with =
"'\"'\"'"s;
1513 const auto useActualDir = argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening;
1514 const auto dirEntries = [&] {
1515 filesystem::directory_iterator
i;
1517 i = filesystem::directory_iterator(actualDir);
1520 i = filesystem::directory_iterator(
".");
1524 for (
const auto &dirEntry : dirEntries) {
1525 if (!completionInfo.completeDirs && dirEntry.is_directory()) {
1528 if (!completionInfo.completeFiles && !dirEntry.is_directory()) {
1531 auto dirEntryName = dirEntry.path().filename().string();
1532 auto hasStartingQuote =
false;
1538 hasStartingQuote =
true;
1539 if (actualDir !=
".") {
1544 if (!hasStartingQuote) {
1547 cout << dirEntryName <<
'\'' <<
' ';
1548 haveFileOrDirCompletions =
true;
1550 }
catch (
const filesystem::filesystem_error &) {
1558 if (haveFileOrDirCompletions) {
1559 cout <<
"; compopt -o filenames";
1564 cout <<
"; compopt -o nospace";
1576 for (
const Argument *arg : args) {
1577 const auto occurrences = arg->occurrences();
1578 if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1579 throw ParseError(
argsToString(
"The argument \"", arg->name(),
"\" mustn't be specified more than ", arg->maxOccurrences(),
1580 (arg->maxOccurrences() == 1 ?
" time." :
" times.")));
1582 if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1583 throw ParseError(
argsToString(
"The argument \"", arg->name(),
"\" must be specified at least ", arg->minOccurrences(),
1584 (arg->minOccurrences() == 1 ?
" time." :
" times.")));
1586 Argument *conflictingArgument =
nullptr;
1587 if (arg->isMainArgument()) {
1588 if (!arg->isCombinable() && arg->isPresent()) {
1592 conflictingArgument = arg->conflictsWithArgument();
1594 if (conflictingArgument) {
1595 throw ParseError(
argsToString(
"The argument \"", conflictingArgument->name(),
"\" can not be combined with \"", arg->name(),
"\"."));
1597 for (
size_t i = 0;
i != occurrences; ++
i) {
1598 if (arg->allRequiredValuesPresent(
i)) {
1601 stringstream ss(stringstream::in | stringstream::out);
1602 ss <<
"Not all parameter for argument \"" << arg->name() <<
"\" ";
1604 ss <<
" (" << (
i + 1) <<
" occurrence) ";
1606 ss <<
"provided. You have to provide the following parameter:";
1607 size_t valueNamesPrint = 0;
1608 for (
const auto &name : arg->m_valueNames) {
1613 while (valueNamesPrint < arg->m_requiredValueCount) {
1614 ss <<
"\nvalue " << (++valueNamesPrint);
1617 throw ParseError(ss.str());
1634 for (
const Argument *arg : args) {
1636 if (arg->m_callbackFunction) {
1637 for (
const auto &occurrence : arg->m_occurrences) {
1638 arg->m_callbackFunction(occurrence);
1649 void ArgumentParser::invokeExit(
int code)
1651 if (m_exitFunction) {
1652 m_exitFunction(code);
1668 :
Argument(
"help",
'h',
"shows this information")
1711 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1712 :
Argument(
"no-color",
'\0',
"disables formatted/colorized output")
1714 :
Argument(
"enable-color",
'\0',
"enables formatted/colorized output")
1717 setCombinable(
true);
1720 setEnvironmentVariable(
"ENABLE_ESCAPE_CODES");
1723 const char *envValue = getenv(environmentVariable());
1727 for (; *envValue; ++envValue) {
1728 switch (*envValue) {
1748 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1759 void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(
const std::vector<Argument *> &argumentPath)
const
1762 ?
argsToString(
"Conversion of top-level value \"", valueToConvert,
"\" to type \"", targetTypeName,
"\" failed: ", errorMessage)
1763 :
argsToString(
"Conversion of value \"", valueToConvert,
"\" (for argument --", argumentPath.back()->name(),
") to type \"",
1764 targetTypeName,
"\" failed: ", errorMessage));
1770 void ArgumentOccurrence::throwNumberOfValuesNotSufficient(
unsigned long valuesToConvert)
const
1772 throw ParseError(
path.empty()
1773 ?
argsToString(
"Expected ", valuesToConvert,
" top-level values to be present but only ",
values.size(),
" have been specified.")
1774 :
argsToString(
"Expected ", valuesToConvert,
" values for argument --",
path.back()->name(),
" to be present but only ",
values.size(),
1775 " have been specified."));