diff --git a/include/clara.hpp b/include/clara.hpp index 381b46a..ac6673b 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -448,9 +448,25 @@ namespace detail { class ParserBase { public: virtual ~ParserBase() = default; - virtual auto validate() const -> Result { return Result::ok(); } - virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; - virtual auto cardinality() const -> size_t { return 1; } + virtual auto validateSettings() const -> Result { return Result::ok(); } + virtual auto validateFinal() const -> Result { return Result::ok(); } + virtual auto canParse() const -> bool { return false; } + virtual auto internalParse( std::string const& exeName, TokenStream const &tokens ) const->InternalParseResult = 0; + + auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult { + auto validationResult = validateSettings(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto result = internalParse( exeName, tokens ); + + // Call this even if parsing failed in order to perform cleanup + validationResult = validateFinal(); + if( result && result.value().type() != ParseResultType::ShortCircuitAll && !validationResult ) + return InternalParseResult( validationResult ); + + return result; + } auto parse( Args const &args ) const -> InternalParseResult { return parse( args.exeName(), TokenStream( args ) ); @@ -475,20 +491,26 @@ namespace detail { std::shared_ptr m_ref; std::string m_hint; std::string m_description; + mutable std::size_t m_count; - explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} + explicit ParserRefImpl( std::shared_ptr const &ref ) + : m_ref( ref ), + m_count( 0 ) + {} public: template ParserRefImpl( T &ref, std::string const &hint ) : m_ref( std::make_shared>( ref ) ), - m_hint( hint ) + m_hint( hint ), + m_count( 0 ) {} template ParserRefImpl( LambdaT const &ref, std::string const &hint ) : m_ref( std::make_shared>( ref ) ), - m_hint(hint) + m_hint( hint ), + m_count( 0 ) {} auto operator()( std::string const &description ) -> DerivedT & { @@ -510,14 +532,31 @@ namespace detail { return m_optionality == Optionality::Optional; } - auto cardinality() const -> size_t override { + virtual auto cardinality() const -> size_t { if( m_ref->isContainer() ) return 0; else return 1; } + auto validateSettings() const -> Result override { + m_count = 0; + return ComposableParserImpl::validateSettings(); + } + + auto validateFinal() const -> Result override { + if( !isOptional() && count() < 1 ) + return Result::runtimeError( "Missing token: " + hint() ); + return ComposableParserImpl::validateFinal(); + } + + auto canParse() const -> bool override { + return (cardinality() == 0 || count() < cardinality()); + } + auto hint() const -> std::string { return m_hint; } + + auto count() const -> std::size_t { return m_count; } }; class ExeName : public ComposableParserImpl { @@ -542,7 +581,7 @@ namespace detail { } // The exe name is not parsed out of the normal tokens, but is handled specially - auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + auto internalParse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); } @@ -566,11 +605,7 @@ namespace detail { public: using ParserRefImpl::ParserRefImpl; - auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { - auto validationResult = validate(); - if( !validationResult ) - return InternalParseResult( validationResult ); - + auto internalParse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { auto remainingTokens = tokens; auto const &token = *remainingTokens; if( token.type != TokenType::Argument ) @@ -582,8 +617,10 @@ namespace detail { auto result = valueRef->setValue( remainingTokens->token ); if( !result ) return InternalParseResult( result ); - else + else { + ++m_count; return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } } }; @@ -643,15 +680,11 @@ namespace detail { using ParserBase::parse; - auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { - auto validationResult = validate(); - if( !validationResult ) - return InternalParseResult( validationResult ); - + auto internalParse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { auto remainingTokens = tokens; if( remainingTokens && remainingTokens->type == TokenType::Option ) { auto const &token = *remainingTokens; - if( isMatch(token.token ) ) { + if( isMatch( token.token ) ) { if( auto flagRef = dynamic_cast( m_ref.get() ) ) { auto result = flagRef->setFlag( true ); if( !result ) @@ -673,13 +706,14 @@ namespace detail { if( result.value() == ParseResultType::ShortCircuitAll ) return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); } + ++m_count; return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); } } return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); } - auto validate() const -> Result override { + auto validateSettings() const -> Result override { if( m_optNames.empty() ) return Result::logicError( "No options supplied to Opt" ); for( auto const &name : m_optNames ) { @@ -693,7 +727,7 @@ namespace detail { return Result::logicError( "Option name must begin with '-'" ); #endif } - return ParserRefImpl::validate(); + return ParserRefImpl::validateSettings(); } }; @@ -805,37 +839,46 @@ namespace detail { return os; } - auto validate() const -> Result override { + auto validateSettings() const -> Result override { for( auto const &opt : m_options ) { - auto result = opt.validate(); + auto result = opt.validateSettings(); if( !result ) return result; } for( auto const &arg : m_args ) { - auto result = arg.validate(); + auto result = arg.validateSettings(); if( !result ) return result; } return Result::ok(); } - using ParserBase::parse; + auto validateFinal() const -> Result override { + for( auto const &opt : m_options ) { + auto result = opt.validateFinal(); + if( !result ) + return result; + } + for( auto const &arg : m_args ) { + auto result = arg.validateFinal(); + if( !result ) + return result; + } + return Result::ok(); + } - auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + using ParserBase::parse; - struct ParserInfo { - ParserBase const* parser = nullptr; - size_t count = 0; - }; + auto internalParse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { const size_t totalParsers = m_options.size() + m_args.size(); assert( totalParsers < 512 ); // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do - ParserInfo parseInfos[512]; + ParserBase const* parsers[512]; { size_t i = 0; - for (auto const &opt : m_options) parseInfos[i++].parser = &opt; - for (auto const &arg : m_args) parseInfos[i++].parser = &arg; + for (auto const &opt : m_options) parsers[i++] = &opt; + for (auto const &arg : m_args) parsers[i++] = &arg; } m_exeName.set( exeName ); @@ -845,14 +888,13 @@ namespace detail { 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() ) { - result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); + auto& parser = parsers[i]; + if( parser->canParse() ) { + result = parser->internalParse(exeName, result.value().remainingTokens()); if (!result) return result; if (result.value().type() != ParseResultType::NoMatch) { tokenParsed = true; - ++parseInfo.count; break; } } @@ -863,7 +905,6 @@ namespace detail { if( !tokenParsed ) return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); } - // !TBD Check missing required options return result; } }; diff --git a/src/ClaraTests.cpp b/src/ClaraTests.cpp index 8a40969..454af20 100644 --- a/src/ClaraTests.cpp +++ b/src/ClaraTests.cpp @@ -120,7 +120,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", "-r", "42", "-n", "Bill", "-d:123.45", "-f", "test1", "test2" } ); CHECK( result ); CHECK( result.value().type() == ParseResultType::Matched ); @@ -129,6 +129,13 @@ TEST_CASE( "Combined parser" ) { REQUIRE( config.m_tests == std::vector { "test1", "test2" } ); CHECK( showHelp == false ); } + SECTION( "missing required" ) { + using namespace Catch::Matchers; + + auto result = parser.parse( Args{ "TestApp", "-n", "Bill", "-d:123.45", "-f", "test1", "test2" } ); + CHECK( !result ); + CHECK_THAT( result.errorMessage(), Contains( "Missing token" ) && Contains( "time|value" ) ); + } SECTION( "help" ) { auto result = parser.parse( Args{ "TestApp", "-?", "-n:NotSet" } ); CHECK( result );