diff --git a/include/slang/util/CommandLine.h b/include/slang/util/CommandLine.h index b67bb63c7..c8821bff4 100644 --- a/include/slang/util/CommandLine.h +++ b/include/slang/util/CommandLine.h @@ -398,6 +398,8 @@ class SLANG_EXPORT CommandLine { void parseStr(std::string_view argList, ParseOptions options, bool& hasArg, std::string& current, SmallVectorBase& 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; diff --git a/source/util/CommandLine.cpp b/source/util/CommandLine.cpp index 9f63f4de6..d3b7f72fb 100644 --- a/source/util/CommandLine.cpp +++ b/source/util/CommandLine.cpp @@ -306,14 +306,14 @@ bool CommandLine::parse(std::span 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; @@ -349,84 +349,45 @@ bool CommandLine::parse(std::span 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) { @@ -443,6 +404,61 @@ bool CommandLine::parse(std::span 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()) diff --git a/tests/unittests/util/CommandLineTests.cpp b/tests/unittests/util/CommandLineTests.cpp index dceb4a902..1b48a54c3 100644 --- a/tests/unittests/util/CommandLineTests.cpp +++ b/tests/unittests/util/CommandLineTests.cpp @@ -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); @@ -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);