Skip to content

Commit

Permalink
Issue catchorg#93: Fix required option
Browse files Browse the repository at this point in the history
When the option is not provided in the commmand line, it raises an
error. The according tests are also added.
  • Loading branch information
bigwater committed Jun 1, 2019
1 parent e306107 commit f28af26
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 3 deletions.
43 changes: 41 additions & 2 deletions include/clara.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include <set>
#include <algorithm>


#if !defined(CLARA_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) )
#define CLARA_PLATFORM_WINDOWS
#endif
Expand Down Expand Up @@ -651,6 +652,14 @@ namespace detail {
return { { oss.str(), m_description } };
}

auto getOptNames() const -> std::string {
std::string optNames = "";
for (auto const &name : m_optNames) {
optNames.append(name + ";");
}
return optNames;
}

auto isMatch( std::string const &optToken ) const -> bool {
auto normalisedToken = normaliseOpt( optToken );
for( auto const &name : m_optNames ) {
Expand All @@ -667,6 +676,20 @@ namespace detail {
if( !validationResult )
return InternalParseResult( validationResult );

if (!isOptional()) {
auto remainingTokens = tokens;
auto requiredOptionMatched = false;
while (remainingTokens) {
if (isMatch(remainingTokens->token)) {
requiredOptionMatched = true;
}
remainingTokens = ++remainingTokens;
}
if (!requiredOptionMatched) {
return InternalParseResult::runtimeError( "The required option " + getOptNames() + " is not provided. ");
}
}

auto remainingTokens = tokens;
if( remainingTokens && remainingTokens->type == TokenType::Option ) {
auto const &token = *remainingTokens;
Expand Down Expand Up @@ -841,7 +864,6 @@ namespace detail {
using ParserBase::parse;

auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override {

struct ParserInfo {
ParserBase const* parser = nullptr;
size_t count = 0;
Expand All @@ -859,13 +881,15 @@ namespace detail {

m_exeName.set( exeName );

std::set<std::string> tokenList;
auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) );
while( result.value().remainingTokens() ) {
bool tokenParsed = false;

for( size_t i = 0; i < totalParsers; ++i ) {
auto& parseInfo = parseInfos[i];
if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) {
tokenList.insert(result.value().remainingTokens()->token);
result = parseInfo.parser->parse(exeName, result.value().remainingTokens());
if (!result)
return result;
Expand All @@ -882,7 +906,22 @@ namespace detail {
if( !tokenParsed )
return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token );
}
// !TBD Check missing required options

for (auto const &opt : m_options) {
if (!opt.isOptional()) {
auto matched = false;
for (auto const &token : tokenList) {
if (opt.isMatch(token)) {
matched = true;
break;
}
}
if (!matched) {
return InternalParseResult::runtimeError( "The required option " + opt.getOptNames() + " is not provided. ");
}
}
}

return result;
}
};
Expand Down
22 changes: 21 additions & 1 deletion src/ClaraTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ TEST_CASE( "Combined parser" ) {
);
}
SECTION( "some args" ) {
auto result = parser.parse( Args{ "TestApp", "-n", "Bill", "-d:123.45", "-f", "test1", "test2" } );
auto result = parser.parse( Args{ "TestApp", "-n", "Bill", "-d:123.45", "-f", "test1", "test2", "-r", "42" } );
CHECK( result );
CHECK( result.value().type() == ParseResultType::Matched );

Expand Down Expand Up @@ -395,6 +395,26 @@ TEST_CASE( "Invalid parsers" )
CHECK( !result );
CHECK_THAT( result.errorMessage(), StartsWith( "Option name must begin with '-'" ) );
}
SECTION( "no required option 1" ) {
bool showHelp = false;
auto cli = Help( showHelp ) | Opt( config.number, "number" )["-n"]["--number"].required();
auto result = cli.parse( { "TestApp" } );
CHECK( !result );
CHECK_THAT( result.errorMessage(), StartsWith("The required option") );
}
SECTION( "no required option 2" ) {
auto cli = Opt( config.number, "number" )["-n"]["--number"].required();
auto result = cli.parse( { "TestApp" } );
CHECK( !result );
CHECK_THAT( result.errorMessage(), StartsWith("The required option") );
}
SECTION( "no required option 3" ) {
auto rseed = -1;
auto cli = Opt( config.number, "number" )["-n"]["--number"].required() | Opt( rseed, "rseed" )["-r"]["--rseed"].required();
auto result = cli.parse( { "TestApp", "-n", "999"} );
CHECK( !result );
CHECK_THAT( result.errorMessage(), StartsWith("The required option") );
}
}

TEST_CASE( "Multiple flags" ) {
Expand Down

0 comments on commit f28af26

Please sign in to comment.