Skip to content
This repository was archived by the owner on Oct 28, 2021. It is now read-only.
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
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