Skip to content
Merged
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
10 changes: 9 additions & 1 deletion args.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -2016,7 +2016,15 @@ namespace args
}
else if(auto group = dynamic_cast<Group*>(child))
{
group->DetectDuplicateFlags(usedShortFlags, usedLongFlags);
// A command opens its own flag namespace and runs its
// own duplicate detection as a separate root, so a flag
// reused either side of a command boundary is not a
// genuine duplicate. Only descend into plain groups
// here; IsGroup() is false for a Command.
if(group->IsGroup())
{
group->DetectDuplicateFlags(usedShortFlags, usedLongFlags);
}
}
}
}
Expand Down
53 changes: 53 additions & 0 deletions test/detect_duplicate_flags.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,55 @@ void testDuplicateLongInTwoGroups()
});
}

void testFlagReuseAcrossCommandBoundary()
{
// A flag inside a command and a flag in an ancestor scope may share a
// name: they live in separate namespaces. Declaring the command flag
// first must not be reported as a duplicate.
args::ArgumentParser parser("This is a test program.", "This goes after the options.");
args::Group commands(parser, "commands");
args::Command run(commands, "run", "run command");
args::Flag run_message(run, "message", "message", {'m', "message"});
test::require_nothrow([&]{
args::Flag top_message(parser, "message", "top message", {'m', "message"});
});
}

void testFlagReuseInNestedGroupAcrossCommandBoundary()
{
args::ArgumentParser parser("This is a test program.", "This goes after the options.");
args::Group commands(parser, "commands");
args::Command run(commands, "run", "run command");
args::Group runopts(run, "run options");
args::Flag run_message(runopts, "message", "message", {'m', "message"});
test::require_nothrow([&]{
args::Flag top_message(parser, "message", "top message", {'m', "message"});
});
}

void testFlagReuseBetweenSiblingCommands()
{
args::ArgumentParser parser("This is a test program.", "This goes after the options.");
args::Group commands(parser, "commands");
args::Command commit(commands, "commit", "commit command");
args::Flag commit_message(commit, "message", "message", {'m', "message"});
args::Command tag(commands, "tag", "tag command");
test::require_nothrow([&]{
args::Flag tag_message(tag, "message", "message", {'m', "message"});
});
}

void testDuplicateWithinCommandStillDetected()
{
args::ArgumentParser parser("This is a test program.", "This goes after the options.");
args::Group commands(parser, "commands");
args::Command run(commands, "run", "run command");
args::Flag run_aone(run, "aone", "test flag", {'a', "aone"});
test::require_throws_as<args::ParseError>([&]{
args::Flag run_atwo(run, "atwo", "test flag", {'a', "atwo"});
});
}

int main()
{
testDuplicateShort();
Expand All @@ -78,5 +127,9 @@ int main()
testDuplicateLongInGroup();
testDuplicateShortInTwoGroups();
testDuplicateLongInTwoGroups();
testFlagReuseAcrossCommandBoundary();
testFlagReuseInNestedGroupAcrossCommandBoundary();
testFlagReuseBetweenSiblingCommands();
testDuplicateWithinCommandStillDetected();

}
47 changes: 47 additions & 0 deletions test/detect_duplicate_flags_noexcept.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,49 @@ void testDuplicateLongInTwoGroups()
test::require(parser.GetError() == args::Error::Usage);
}

void testFlagReuseAcrossCommandBoundary()
{
args::ArgumentParser parser("This is a test program.", "This goes after the options.");
args::Group commands(parser, "commands");
args::Command run(commands, "run", "run command");
args::Flag run_message(run, "message", "message", {'m', "message"});
args::Flag top_message(parser, "message", "top message", {'m', "message"});
test::require(parser.GetError() == args::Error::None);
}

void testFlagReuseInNestedGroupAcrossCommandBoundary()
{
args::ArgumentParser parser("This is a test program.", "This goes after the options.");
args::Group commands(parser, "commands");
args::Command run(commands, "run", "run command");
args::Group runopts(run, "run options");
args::Flag run_message(runopts, "message", "message", {'m', "message"});
args::Flag top_message(parser, "message", "top message", {'m', "message"});
test::require(parser.GetError() == args::Error::None);
}

void testFlagReuseBetweenSiblingCommands()
{
args::ArgumentParser parser("This is a test program.", "This goes after the options.");
args::Group commands(parser, "commands");
args::Command commit(commands, "commit", "commit command");
args::Flag commit_message(commit, "message", "message", {'m', "message"});
args::Command tag(commands, "tag", "tag command");
args::Flag tag_message(tag, "message", "message", {'m', "message"});
test::require(parser.GetError() == args::Error::None);
}

void testDuplicateWithinCommandStillDetected()
{
args::ArgumentParser parser("This is a test program.", "This goes after the options.");
args::Group commands(parser, "commands");
args::Command run(commands, "run", "run command");
args::Flag run_aone(run, "aone", "test flag", {'a', "aone"});
args::Flag run_atwo(run, "atwo", "test flag", {'a', "atwo"});
// The duplicate is recorded on the offending flag when it is added.
test::require(run_atwo.GetError() == args::Error::Usage);
}

int main()
{
testDuplicateShort();
Expand All @@ -72,4 +115,8 @@ int main()
testDuplicateLongInGroup();
testDuplicateShortInTwoGroups();
testDuplicateLongInTwoGroups();
testFlagReuseAcrossCommandBoundary();
testFlagReuseInNestedGroupAcrossCommandBoundary();
testFlagReuseBetweenSiblingCommands();
testDuplicateWithinCommandStillDetected();
}