Skip to content

Commit

Permalink
Make cmdIgnore and cmdRename support equals-value argument commands
Browse files Browse the repository at this point in the history
  • Loading branch information
MikePopoloski committed Jan 20, 2024
1 parent 40d4aca commit a50c4a3
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 69 deletions.
2 changes: 2 additions & 0 deletions include/slang/util/CommandLine.h
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ class SLANG_EXPORT CommandLine {
void parseStr(std::string_view argList, ParseOptions options, bool& hasArg,
std::string& current, SmallVectorBase<std::string>& storage);

void handleArg(std::string_view arg, Option*& expectingVal, std::string& expectingValName,
bool& hadUnknowns, ParseOptions options);
void handlePlusArg(std::string_view arg, ParseOptions options, bool& hadUnknowns);

Option* findOption(std::string_view arg, std::string_view& value) const;
Expand Down
150 changes: 83 additions & 67 deletions source/util/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,14 +306,14 @@ bool CommandLine::parse(std::span<const std::string_view> args, ParseOptions opt
}

Option* expectingVal = nullptr;
std::string_view expectingValName;
std::string expectingValName;
bool doubleDash = false;
bool hadUnknowns = false;
std::string_view firstPositional;

int skip = 0;
for (auto arg : args) {
// Skip N arguments if needed (set by the cmdIgnore feature)
// Skip N arguments if needed (set by the cmdIgnore feature).
if (skip) {
skip--;
continue;
Expand Down Expand Up @@ -349,84 +349,45 @@ bool CommandLine::parse(std::span<const std::string_view> args, ParseOptions opt
continue;
}

// Check if arg is in the list of commands to skip.
if (!cmdIgnore.empty()) {
// If we ignore a vendor command of the form +xx ,
// we match on any +xx+yyy command as +yy is the command's argument.
// Check if arg is in the list of commands to skip or translate.
if (!cmdIgnore.empty() || !cmdRename.empty()) {
std::string_view ignoreArg = arg;
std::string_view remainder;
if (arg[0] == '+') {
size_t plusIndex = arg.substr(1).find_first_of('+');
// If we ignore a vendor command of the form +xx ,
// we match on any +xx+yyy command as +yy is the command's argument.
size_t plusIndex = arg.find_first_of('+', 1);
if (plusIndex != std::string_view::npos) {
ignoreArg = arg.substr(0, plusIndex +
1); // +1 because we started from arg.substr(1)
ignoreArg = arg.substr(0, plusIndex);
remainder = arg.substr(plusIndex);
}
}
else {
// Otherwise we look up to the first equals for the name to ignore.
size_t equalsIndex = arg.find_first_of('=');
if (equalsIndex != std::string_view::npos) {
ignoreArg = arg.substr(0, equalsIndex);
remainder = arg.substr(equalsIndex);
}
}

if (auto it = cmdIgnore.find(std::string(ignoreArg)); it != cmdIgnore.end()) {
// if yes, find how many args to skip
auto lookupStr = std::string(ignoreArg);
if (auto it = cmdIgnore.find(lookupStr); it != cmdIgnore.end()) {
// If yes, find how many args to skip.
skip = it->second;
continue;
}
}

// Check if arg is in the list of commands to translate.
if (!cmdRename.empty()) {
if (auto it = cmdRename.find(std::string(arg)); it != cmdRename.end()) {
// if yes, rename argument
arg = it->second;
if (auto it = cmdRename.find(lookupStr); it != cmdRename.end()) {
// If yes, rename argument.
auto renamed = it->second + std::string(remainder);
handleArg(renamed, expectingVal, expectingValName, hadUnknowns, options);
continue;
}
}

// Handle plus args, which are treated differently from all others.
if (arg[0] == '+') {
handlePlusArg(arg, options, hadUnknowns);
continue;
}

// Get the raw name without leading dashes.
bool longName = false;
std::string_view name = arg.substr(1);
if (name[0] == '-') {
longName = true;
name = name.substr(1);
}

std::string_view value;
auto option = findOption(name, value);

// If we didn't find the option and there was only a single dash,
// maybe this was actually a group of single-char options or a prefixed value.
if (!option && !longName) {
option = tryGroupOrPrefix(name, value, options);
if (option)
arg = name;
}

// If we still didn't find it, that's an error.
if (!option) {
// Try to find something close to give a better error message.
auto error = fmt::format("{}: unknown command line argument '{}'"sv, programName, arg);
auto nearest = findNearestMatch(arg);
if (!nearest.empty())
error += fmt::format(", did you mean '{}'?"sv, nearest);

hadUnknowns = true;
errors.emplace_back(std::move(error));
continue;
}

// Otherwise, we found what we wanted. If we have a value already, go ahead
// and set it. Otherwise if we're expecting a value, assume that it will come
// in the next argument.
if (value.empty() && option->expectsValue()) {
expectingVal = option;
expectingValName = arg;
}
else {
std::string result = option->set(arg, value, options.ignoreDuplicates);
if (!result.empty())
errors.emplace_back(fmt::format("{}: {}", programName, result));
}
// Otherwise just handle the argument.
handleArg(arg, expectingVal, expectingValName, hadUnknowns, options);
}

if (expectingVal) {
Expand All @@ -443,6 +404,61 @@ bool CommandLine::parse(std::span<const std::string_view> args, ParseOptions opt
return errors.empty();
}

void CommandLine::handleArg(std::string_view arg, Option*& expectingVal,
std::string& expectingValName, bool& hadUnknowns,
ParseOptions options) {
// Handle plus args, which are treated differently from all others.
if (arg[0] == '+') {
handlePlusArg(arg, options, hadUnknowns);
return;
}

// Get the raw name without leading dashes.
bool longName = false;
std::string_view name = arg.substr(1);
if (name[0] == '-') {
longName = true;
name = name.substr(1);
}

std::string_view value;
auto option = findOption(name, value);

// If we didn't find the option and there was only a single dash,
// maybe this was actually a group of single-char options or a prefixed value.
if (!option && !longName) {
option = tryGroupOrPrefix(name, value, options);
if (option)
arg = name;
}

// If we still didn't find it, that's an error.
if (!option) {
// Try to find something close to give a better error message.
auto error = fmt::format("{}: unknown command line argument '{}'"sv, programName, arg);
auto nearest = findNearestMatch(arg);
if (!nearest.empty())
error += fmt::format(", did you mean '{}'?"sv, nearest);

hadUnknowns = true;
errors.emplace_back(std::move(error));
return;
}

// Otherwise, we found what we wanted. If we have a value already, go ahead
// and set it. Otherwise if we're expecting a value, assume that it will come
// in the next argument.
if (value.empty() && option->expectsValue()) {
expectingVal = option;
expectingValName = arg;
}
else {
std::string result = option->set(arg, value, options.ignoreDuplicates);
if (!result.empty())
errors.emplace_back(fmt::format("{}: {}", programName, result));
}
}

std::string CommandLine::getHelpText(std::string_view overview) const {
std::string result;
if (!overview.empty())
Expand Down
5 changes: 3 additions & 2 deletions tests/unittests/util/CommandLineTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -468,8 +468,9 @@ TEST_CASE("Test CommandLine -- check setIgnoreCommand()") {
cmdLine.addIgnoreCommand("--xxx,0");
cmdLine.addIgnoreCommand("--yyy,2");
cmdLine.addIgnoreCommand("+zzz,0");
cmdLine.addIgnoreCommand("-baz,0");

CHECK(cmdLine.parse("prog --yyy --foo 456 --foo 123 --xxx +zzz+123+abc+456"));
CHECK(cmdLine.parse("prog --yyy --foo 456 --foo 123 --xxx +zzz+123+abc+456 -baz=blah"));
// --foo 456 is skipped because it's not a real flag, it's --yyy's two parameters,
// which are ignored
CHECK(foo == 123);
Expand All @@ -488,7 +489,7 @@ TEST_CASE("Test CommandLine -- check setRenameCommand()") {
cmdLine.addRenameCommand("--xxx,--foo");
cmdLine.addRenameCommand("--yyy,--bar");

CHECK(cmdLine.parse("prog --xxx 123 --yyy 456"));
CHECK(cmdLine.parse("prog --xxx 123 --yyy=456"));
CHECK(foo == 123);
CHECK(bar == 456);

Expand Down

0 comments on commit a50c4a3

Please sign in to comment.