Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/tscore/ArgParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ class ArgParser
void validate_mutex_groups(Arguments &ret) const;
// Helper method to validate option dependencies
void validate_dependencies(Arguments &ret) const;
// Helper method to apply default values for options not explicitly set
void apply_option_defaults(Arguments &ret) const;
// The command name and help message
std::string _name;
std::string _description;
Expand Down
14 changes: 13 additions & 1 deletion src/tscore/ArgParser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,14 @@ ArgParser::Command::append_option_data(Arguments &ret, AP_StrVec &args, int inde
help_message(std::to_string(_option_list.at(it.first).arg_num) + " arguments expected by " + it.first);
}
}
// put in the default value of options
}

// Apply default values for options not explicitly set by the user.
// This must be called AFTER validate_dependencies() so that default values
// (e.g. --timeout "0") don't falsely trigger dependency checks.
void
ArgParser::Command::apply_option_defaults(Arguments &ret) const
{
for (const auto &it : _option_list) {
if (!it.second.default_value.empty() && ret.get(it.second.key).empty()) {
std::istringstream ss(it.second.default_value);
Expand Down Expand Up @@ -771,6 +778,11 @@ ArgParser::Command::parse(Arguments &ret, AP_StrVec &args)

// Validate option dependencies
validate_dependencies(ret);

// Apply default values after validation so that defaults don't
// trigger dependency checks (e.g. --timeout with default "0"
// should not require --monitor when not explicitly used).
apply_option_defaults(ret);
}

if (command_called) {
Expand Down
69 changes: 69 additions & 0 deletions src/tscore/unit_tests/test_ArgParser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,72 @@ TEST_CASE("Invoke test", "[invoke]")
parsed_data.invoke();
REQUIRE(global == 2);
}

TEST_CASE("Case sensitive short options", "[parse]")
{
ts::ArgParser cs_parser;
cs_parser.add_global_usage("test_prog [--SWITCH]");

// Add a command with two options that differ only in case: -t and -T
ts::ArgParser::Command &cmd = cs_parser.add_command("process", "process data");
cmd.add_option("--tag", "-t", "a label", "", 1, "");
cmd.add_option("--threshold", "-T", "a numeric value", "", 1, "100");

ts::Arguments parsed;

// Use lowercase -t: should set "tag" only
const char *argv1[] = {"test_prog", "process", "-t", "my_tag", nullptr};
parsed = cs_parser.parse(argv1);
REQUIRE(parsed.get("tag") == true);
REQUIRE(parsed.get("tag").value() == "my_tag");
// threshold should still have its default
REQUIRE(parsed.get("threshold").value() == "100");

// Use uppercase -T: should set "threshold" only
const char *argv2[] = {"test_prog", "process", "-T", "200", nullptr};
parsed = cs_parser.parse(argv2);
REQUIRE(parsed.get("threshold") == true);
REQUIRE(parsed.get("threshold").value() == "200");
// tag should be empty (no default)
REQUIRE(parsed.get("tag").value() == "");

// Use both -t and -T together
const char *argv3[] = {"test_prog", "process", "-t", "foo", "-T", "500", nullptr};
parsed = cs_parser.parse(argv3);
REQUIRE(parsed.get("tag") == true);
REQUIRE(parsed.get("tag").value() == "foo");
REQUIRE(parsed.get("threshold") == true);
REQUIRE(parsed.get("threshold").value() == "500");
}

TEST_CASE("with_required does not trigger on default values", "[parse]")
{
ts::ArgParser parser;
parser.add_global_usage("test_prog [OPTIONS]");

ts::ArgParser::Command &cmd = parser.add_command("scan", "scan targets");
cmd.add_option("--tag", "-t", "a label", "", 1, "");
cmd.add_option("--verbose", "-v", "enable verbose output");
cmd.add_option("--threshold", "-T", "a numeric value", "", 1, "100").with_required("--verbose");

// -t alone should NOT trigger --threshold's dependency on --verbose.
// The default value "100" for --threshold must not count as "explicitly used".
const char *argv1[] = {"test_prog", "scan", "-t", "my_tag", nullptr};
ts::Arguments parsed = parser.parse(argv1);
REQUIRE(parsed.get("tag").value() == "my_tag");
// threshold default should still be applied after validation
REQUIRE(parsed.get("threshold").value() == "100");

// -T with -v should work fine
const char *argv2[] = {"test_prog", "scan", "-T", "200", "-v", nullptr};
parsed = parser.parse(argv2);
REQUIRE(parsed.get("threshold").value() == "200");
REQUIRE(parsed.get("verbose") == true);

// -t and -T together with -v should work
const char *argv3[] = {"test_prog", "scan", "-t", "foo", "-T", "300", "-v", nullptr};
parsed = parser.parse(argv3);
REQUIRE(parsed.get("tag").value() == "foo");
REQUIRE(parsed.get("threshold").value() == "300");
REQUIRE(parsed.get("verbose") == true);
}