From be37daf1391c82f78c940a09921c2e5097afaf69 Mon Sep 17 00:00:00 2001 From: Baruch Date: Mon, 3 Jul 2017 12:43:49 +0300 Subject: [PATCH 01/16] Fix usage test of cli --- src/ClaraTests.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/ClaraTests.cpp b/src/ClaraTests.cpp index c9f0dc8..f259eca 100644 --- a/src/ClaraTests.cpp +++ b/src/ClaraTests.cpp @@ -255,7 +255,20 @@ TEST_CASE( "cmdline" ) { REQUIRE( config.secondPos == "2nd" ); } SECTION( "usage" ) { - std::cout << cli << std::endl; + std::ostringstream oss; + oss << cli; + auto usage = oss.str(); + REQUIRE(usage == + "usage:\n" + " [ ] options\n" + "\n" + "where options are:\n" + " -o, --output specifies output file\n" + " -n \n" + " -i An index, which is an integer between 0 and 10,\n" + " inclusive\n" + " -f A flag\n" + ); } } @@ -290,4 +303,4 @@ TEST_CASE( "Multiple flags" ) { CHECK(b); CHECK(c); } -} \ No newline at end of file +} From 05767d6a0da1d5465da1e54251908fe464b1fa1f Mon Sep 17 00:00:00 2001 From: Baruch Date: Tue, 3 Oct 2017 10:41:14 +0300 Subject: [PATCH 02/16] Fix non standard compliant usage of VLA --- include/clara.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/clara.hpp b/include/clara.hpp index 2d0ab06..18b2cb3 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -811,7 +811,7 @@ namespace detail { size_t count = 0; }; const size_t totalParsers = m_options.size() + m_args.size(); - ParserInfo parseInfos[totalParsers]; + std::vector parseInfos(totalParsers); size_t i = 0; for( auto const& opt : m_options ) parseInfos[i++].parser = &opt; for( auto const& arg : m_args ) parseInfos[i++].parser = &arg; From f2b993ebec10a66d7b71550b08b16a04056e93f2 Mon Sep 17 00:00:00 2001 From: baruch Date: Thu, 19 Oct 2017 14:52:24 +0300 Subject: [PATCH 03/16] Support required options --- include/clara.hpp | 118 ++++++++++++++++++++++++++++++--------------- src/ClaraTests.cpp | 9 +++- 2 files changed, 86 insertions(+), 41 deletions(-) diff --git a/include/clara.hpp b/include/clara.hpp index 2af4419..c26597e 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -450,9 +450,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 ) ); @@ -474,20 +490,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 & { @@ -509,14 +531,27 @@ 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 validateFinal() const -> Result override { + if( !isOptional() && count() < 1 ) + return Result::runtimeError( "Missing token: " + hint() ); + m_count = 0; + 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 { @@ -541,7 +576,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 ) ); } @@ -565,11 +600,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 ) @@ -578,8 +609,10 @@ namespace detail { auto result = m_ref->setValue( remainingTokens->token ); if( !result ) return InternalParseResult( result ); - else + else { + ++m_count; return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } } }; @@ -641,22 +674,19 @@ 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( m_ref->isFlag() ) { auto result = m_ref->setFlag( true ); if( !result ) return InternalParseResult( result ); if( result.value() == ParseResultType::ShortCircuitAll ) return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); - } else { + } + else { ++remainingTokens; if( !remainingTokens ) return InternalParseResult::runtimeError( "Expected argument following " + token.token ); @@ -669,13 +699,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 ) { @@ -684,7 +715,7 @@ namespace detail { if( name[0] != '-' && name[0] != '/' ) return Result::logicError( "Option name must begin with '-' or '/'" ); } - return ParserRefImpl::validate(); + return ParserRefImpl::validateSettings(); } }; @@ -788,33 +819,42 @@ 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(); - ParserInfo parseInfos[totalParsers]; + ParserBase const* parsers[totalParsers]; 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 ); @@ -822,14 +862,13 @@ namespace detail { while( result.value().remainingTokens() ) { bool tokenParsed = false; - for( auto& parseInfo : parseInfos ) { - if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { - result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); + for( auto& parser : parsers ) { + 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; } } @@ -840,7 +879,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 83ba2fc..7f48094 100644 --- a/src/ClaraTests.cpp +++ b/src/ClaraTests.cpp @@ -118,7 +118,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 ); @@ -127,6 +127,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 ); From b76c4c33566fb7cd62b5482c15b07bf985477bcd Mon Sep 17 00:00:00 2001 From: baruch Date: Thu, 19 Oct 2017 15:49:07 +0300 Subject: [PATCH 04/16] Pedntic correction of size_t to std::size_t --- include/clara.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/clara.hpp b/include/clara.hpp index cb71e7c..53e86f4 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -102,7 +102,7 @@ namespace detail { } else { if( next[1] != '-' && next.size() > 2 ) { std::string opt = "- "; - for( size_t i = 1; i < next.size(); ++i ) { + for( std::size_t i = 1; i < next.size(); ++i ) { opt[1] = next[i]; m_tokenBuffer.push_back( { TokenType::Option, opt } ); } @@ -127,7 +127,7 @@ namespace detail { return !m_tokenBuffer.empty() || it != itEnd; } - auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } + auto count() const -> std::size_t { return m_tokenBuffer.size() + (itEnd - it); } auto operator*() const -> Token { assert( !m_tokenBuffer.empty() ); @@ -452,7 +452,7 @@ namespace detail { 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 cardinality() const -> std::size_t { return 1; } auto parse( Args const &args ) const -> InternalParseResult { return parse( args.exeName(), TokenStream( args ) ); @@ -509,7 +509,7 @@ namespace detail { return m_optionality == Optionality::Optional; } - auto cardinality() const -> size_t override { + auto cardinality() const -> std::size_t override { if( m_ref->isContainer() ) return 0; else @@ -769,8 +769,8 @@ namespace detail { } auto rows = getHelpColumns(); - size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; - size_t optWidth = 0; + std::size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; + std::size_t optWidth = 0; for( auto const &cols : rows ) optWidth = (std::max)(optWidth, cols.left.size() + 2); @@ -808,11 +808,11 @@ namespace detail { struct ParserInfo { ParserBase const* parser = nullptr; - size_t count = 0; + std::size_t count = 0; }; - const size_t totalParsers = m_options.size() + m_args.size(); + const std::size_t totalParsers = m_options.size() + m_args.size(); std::vector parseInfos(totalParsers); - size_t i = 0; + std::size_t i = 0; for( auto const& opt : m_options ) parseInfos[i++].parser = &opt; for( auto const& arg : m_args ) parseInfos[i++].parser = &arg; From c7ec97d97e3f9b13980c006c51d71bf95b2ffd59 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Sat, 21 Oct 2017 09:11:03 +0200 Subject: [PATCH 05/16] Fixed leakage of / as opt prefix from Windows --- include/clara.hpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/include/clara.hpp b/include/clara.hpp index 04e94d1..9ccf55f 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -78,6 +78,14 @@ namespace detail { std::string token; }; + inline auto isOptPrefix( char c ) -> bool { + return c == '-' +#ifdef CLARA_PLATFORM_WINDOWS + || c == '/' +#endif + ; + } + // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled class TokenStream { using Iterator = std::vector::const_iterator; @@ -94,7 +102,7 @@ namespace detail { if( it != itEnd ) { auto const &next = *it; - if( next[0] == '-' || next[0] == '/' ) { + if( isOptPrefix( next[0] ) ) { auto delimiterPos = next.find_first_of( " :=" ); if( delimiterPos != std::string::npos ) { m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); @@ -617,9 +625,11 @@ namespace detail { }; inline auto normaliseOpt( std::string const &optName ) -> std::string { +#ifdef CLARA_PLATFORM_WINDOWS if( optName[0] == '/' ) return "-" + optName.substr( 1 ); else +#endif return optName; } @@ -660,11 +670,7 @@ namespace detail { } auto isMatch( std::string const &optToken ) const -> bool { -#ifdef CLARA_PLATFORM_WINDOWS auto normalisedToken = normaliseOpt( optToken ); -#else - auto const &normalisedToken = optToken; -#endif for( auto const &name : m_optNames ) { if( normaliseOpt( name ) == normalisedToken ) return true; @@ -712,8 +718,13 @@ namespace detail { for( auto const &name : m_optNames ) { if( name.empty() ) return Result::logicError( "Option name cannot be empty" ); +#ifdef CLARA_PLATFORM_WINDOWS if( name[0] != '-' && name[0] != '/' ) return Result::logicError( "Option name must begin with '-' or '/'" ); +#else + if( name[0] != '-' ) + return Result::logicError( "Option name must begin with '-'" ); +#endif } return ParserRefImpl::validateSettings(); } From d31850a85dc183b50730da28abb55dcaaa3e6acf Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 26 Oct 2017 22:54:41 +0200 Subject: [PATCH 06/16] #29 Extend handling of arguments * allow for hidden arguments and options * list arguments in the help output if they have a description * make Args handle the case when argv doesn't contain the executable as its first element, which is useful for Win32 programs --- include/clara.hpp | 86 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 25 deletions(-) diff --git a/include/clara.hpp b/include/clara.hpp index 9ccf55f..81341e3 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #if !defined(CLARA_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) @@ -52,9 +53,12 @@ namespace detail { std::vector m_args; public: - Args( int argc, char *argv[] ) { - m_exeName = argv[0]; - for( int i = 1; i < argc; ++i ) + // exeName is especially useful for Win32 programs which get passed the whole commandline + // (argv returned from `CommandLineToArgvW` doesn't contain the executable path) + Args( int argc, char *argv[], std::string exeName = std::string{} ) { + bool exeOffArgv = exeName.empty(); + m_exeName = exeOffArgv ? argv[0] : move(exeName); + for( int i = exeOffArgv ? 1 : 0; i < argc; ++i ) m_args.push_back( argv[i] ); } @@ -63,7 +67,7 @@ namespace detail { m_args( args.begin()+1, args.end() ) {} - auto exeName() const -> std::string { + auto exeName() const -> std::string const& { return m_exeName; } }; @@ -239,7 +243,7 @@ namespace detail { explicit operator bool() const { return m_type == ResultBase::Ok; } auto type() const -> ResultBase::Type { return m_type; } - auto errorMessage() const -> std::string { return m_errorMessage; } + auto errorMessage() const -> std::string const& { return m_errorMessage; } protected: virtual void enforceOk() const { @@ -456,7 +460,11 @@ namespace detail { struct Parser; class ParserBase { + protected: + bool m_hidden; + public: + ParserBase() : m_hidden( false ) {} virtual ~ParserBase() = default; virtual auto validateSettings() const -> Result { return Result::ok(); } virtual auto validateFinal() const -> Result { return Result::ok(); } @@ -535,6 +543,11 @@ namespace detail { return static_cast( *this ); }; + auto hidden() -> DerivedT & { + m_hidden = true; + return static_cast(*this); + }; + auto isOptional() const -> bool { return m_optionality == Optionality::Optional; } @@ -557,7 +570,7 @@ namespace detail { return (cardinality() == 0 || count() < cardinality()); } - auto hint() const -> std::string { return m_hint; } + auto hint() const -> std::string const& { return m_hint; } auto count() const -> std::size_t { return m_count; } }; @@ -588,12 +601,12 @@ namespace detail { return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); } - auto name() const -> std::string { return *m_name; } - auto set( std::string const& newName ) -> ParserResult { + auto name() const -> std::string const& { return *m_name; } + auto set( std::string newName ) -> ParserResult { auto lastSlash = newName.find_last_of( "\\/" ); auto filename = ( lastSlash == std::string::npos ) - ? newName + ? move( newName ) : newName.substr( lastSlash+1 ); *m_name = filename; @@ -608,6 +621,16 @@ namespace detail { public: using ParserRefImpl::ParserRefImpl; + auto getHelpColumns() const -> std::vector { + if( m_description.empty() || m_hidden ) + return {}; + else { + std::ostringstream oss; + oss << " <" << m_hint << ">"; + return { { oss.str(), m_description} }; + } + } + auto internalParse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { auto remainingTokens = tokens; auto const &token = *remainingTokens; @@ -655,6 +678,9 @@ namespace detail { } auto getHelpColumns() const -> std::vector { + if( m_hidden ) + return {}; + std::ostringstream oss; bool first = true; for( auto const &opt : m_optNames ) { @@ -777,10 +803,12 @@ namespace detail { return Parser( *this ) |= other; } - auto getHelpColumns() const -> std::vector { + template + auto getHelpColumns( Parsers const &p ) const -> std::vector { std::vector cols; - for (auto const &o : m_options) { + for (auto const &o : p) { auto childCols = o.getHelpColumns(); + cols.reserve( cols.size() + childCols.size() ); cols.insert( cols.end(), childCols.begin(), childCols.end() ); } return cols; @@ -807,22 +835,30 @@ namespace detail { os << "]"; if( !m_options.empty() ) os << " options"; - os << "\n\nwhere options are:" << std::endl; + os << "\n"; } - auto rows = getHelpColumns(); - std::size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; - std::size_t optWidth = 0; - for( auto const &cols : rows ) - optWidth = (std::max)(optWidth, cols.left.size() + 2); - - for( auto const &cols : rows ) { - auto row = - TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + - TextFlow::Spacer(4) + - TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); - os << row << std::endl; - } + auto streamHelpColumns = [&os]( std::vector const &rows, const std::string &header ) { + if( !rows.empty() ) { + os << header << std::endl; + + std::size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; + std::size_t width = 0; + for( auto const &cols : rows ) + width = (std::max)(width, cols.left.size() + 2); + + for( auto const &cols : rows ) { + auto row = + TextFlow::Column( cols.left ).width( width ).indent( 2 ) + + TextFlow::Spacer(4) + + TextFlow::Column( cols.right ).width( consoleWidth - 7 - width ); + os << row << std::endl; + } + } + }; + + streamHelpColumns( getHelpColumns( m_args ), "\nwhere arguments are:" ); + streamHelpColumns( getHelpColumns( m_options ), "\nwhere options are:" ); } friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { From 42c8bad87bd04b85eb15cb4157040a155e93300e Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 27 Oct 2017 20:52:54 +0200 Subject: [PATCH 07/16] #31 ability for subcommands --- include/clara.hpp | 118 +++++++++++++++++++++++++++++++++++++++++++-- src/ClaraTests.cpp | 100 +++++++++++++++++++++++++++++++++++++- 2 files changed, 212 insertions(+), 6 deletions(-) diff --git a/include/clara.hpp b/include/clara.hpp index 81341e3..a902de0 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -577,6 +577,7 @@ namespace detail { class ExeName : public ComposableParserImpl { std::shared_ptr m_name; + std::shared_ptr m_description; std::shared_ptr m_ref; template @@ -585,7 +586,10 @@ namespace detail { } public: - ExeName() : m_name( std::make_shared( "" ) ) {} + ExeName() + : m_name( std::make_shared( "" ) ), + m_description( std::make_shared() ) + {} explicit ExeName( std::string &ref ) : ExeName() { m_ref = std::make_shared>( ref ); @@ -602,7 +606,9 @@ namespace detail { } auto name() const -> std::string const& { return *m_name; } - auto set( std::string newName ) -> ParserResult { + auto description( std::string d ) -> void { *m_description = move(d); } + auto description() const -> std::string const& { return *m_description; } + auto set( std::string newName, bool updateRef = true ) -> ParserResult { auto lastSlash = newName.find_last_of( "\\/" ); auto filename = ( lastSlash == std::string::npos ) @@ -610,7 +616,7 @@ namespace detail { : newName.substr( lastSlash+1 ); *m_name = filename; - if( m_ref ) + if( m_ref && updateRef ) return m_ref->setValue( filename ); else return ParserResult::ok( ParseResultType::Matched ); @@ -774,6 +780,8 @@ namespace detail { struct Parser : ParserBase { mutable ExeName m_exeName; + bool m_isSubcmd = false; + std::vector m_cmds; std::vector m_options; std::vector m_args; @@ -795,6 +803,7 @@ namespace detail { auto operator|=( Parser const &other ) -> Parser & { m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); + m_cmds.insert(m_cmds.end(), other.m_cmds.begin(), other.m_cmds.end()); return *this; } @@ -815,6 +824,10 @@ namespace detail { } void writeToStream( std::ostream &os ) const { + // print banner + if( !m_exeName.description().empty() ) { + os << m_exeName.description() << std::endl << std::endl; + } if (!m_exeName.name().empty()) { os << "usage:\n" << " " << m_exeName.name() << " "; bool required = true, first = true; @@ -835,6 +848,11 @@ namespace detail { os << "]"; if( !m_options.empty() ) os << " options"; + if( !m_cmds.empty() ) { + if( !m_options.empty() ) + os << " |"; + os << " subcommand"; + } os << "\n"; } @@ -859,6 +877,22 @@ namespace detail { streamHelpColumns( getHelpColumns( m_args ), "\nwhere arguments are:" ); streamHelpColumns( getHelpColumns( m_options ), "\nwhere options are:" ); + + if( !m_cmds.empty() ) { + std::vector cmdCols; + { + cmdCols.reserve( m_cmds.size() ); + for( auto const &cmd : m_cmds ) { + if( cmd.m_hidden ) + continue; + const std::string &d = cmd.m_exeName.description(); + // first line + cmdCols.push_back({ cmd.m_exeName.name(), d.substr(0, d.find('\n')) }); + } + } + + streamHelpColumns(cmdCols, "\nwhere subcommands are:"); + } } friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { @@ -894,18 +928,38 @@ namespace detail { return Result::ok(); } + auto findCmd( const std::string& cmdName ) const -> Parser const* { + for( const Parser& cmd : m_cmds ) { + if( cmd.m_exeName.name() == cmdName ) + return &cmd; + } + return nullptr; + } + using ParserBase::parse; auto internalParse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + if( !m_cmds.empty() && tokens ) { + std::string subCommand = tokens->token; + if( Parser const *cmd = findCmd( subCommand )) { + return cmd->parse( subCommand, tokens ); + } + } + const std::size_t totalParsers = m_options.size() + m_args.size(); std::vector parsers(totalParsers); std::size_t i = 0; for( auto const& opt : m_options ) parsers[i++] = &opt; for( auto const& arg : m_args ) parsers[i++] = &arg; - m_exeName.set( exeName ); + if( m_isSubcmd ) + m_exeName.set( tokens->token ); + else + m_exeName.set( exeName ); - auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + auto result = InternalParseResult::ok( m_isSubcmd ? + ParseState( ParseResultType::Matched, ++TokenStream( tokens ) ) : + ParseState( ParseResultType::NoMatch, tokens ) ); while( result.value().remainingTokens() ) { bool tokenParsed = false; @@ -935,12 +989,66 @@ namespace detail { auto ComposableParserImpl::operator|( T const &other ) const -> Parser { return Parser() | static_cast( *this ) | other; } + + struct Cmd : public Parser { + Cmd( std::string& ref, std::string cmdName ) : Parser() { + ExeName exe{ ref }; + exe.set( move( cmdName ), false ); + m_exeName = exe; + m_isSubcmd = true; + } + + template + Cmd( Lambda const& ref, std::string cmdName ) : Parser() { + ExeName exe{ ref }; + exe.set( move( cmdName ), false ); + m_exeName = exe; + m_isSubcmd = true; + } + + auto hidden() -> Cmd& { + m_hidden = true; + return *this; + }; + + auto operator()( std::string description ) -> Cmd& { + m_exeName.description( move( description ) ); + return *this; + }; + }; + + template + auto operator,( ComposableParserImpl const &l, Parser const &r ) -> Parser = delete; + template + auto operator,( Parser const &l, ComposableParserImpl const &r ) -> Parser = delete; + template + auto operator,( ComposableParserImpl const &l, ComposableParserImpl const &r ) -> Parser = delete; + + // concatenate parsers as subcommands; + // precondition: one or both must be subcommand parsers + inline auto operator,( Parser const &l, Parser const &r ) -> Parser { + assert( l.m_isSubcmd || r.m_isSubcmd ); + + Parser const *p1 = &l, *p2 = &r; + if ( p1->m_isSubcmd && !p2->m_isSubcmd ) { + std::swap( p1, p2 ); + } + + Parser p = p1->m_isSubcmd ? Parser{} : Parser{ *p1 }; + if ( p1->m_isSubcmd ) + p.m_cmds.push_back( *p1 ); + p.m_cmds.push_back( *p2 ); + return p; + } + } // namespace detail // A Combined parser using detail::Parser; +using detail::Cmd; + // A parser for options using detail::Opt; diff --git a/src/ClaraTests.cpp b/src/ClaraTests.cpp index 738e84d..93dba43 100644 --- a/src/ClaraTests.cpp +++ b/src/ClaraTests.cpp @@ -1,4 +1,4 @@ -#include "clara.hpp" +#include #include "catch.hpp" @@ -337,3 +337,101 @@ TEST_CASE( "Unrecognised opts" ) { CHECK( !result ); CHECK_THAT( result.errorMessage(), Contains( "Unrecognised token") && Contains( "-b" ) ); } + +TEST_CASE( "Subcommands" ) { + using namespace Catch::Matchers; + + std::string subcommand, subArg; + bool showHelp = false, subOpt = false; + + auto cli = ( + // create a full parser + Parser{} | Help{ showHelp } + , Cmd{ subcommand, "subcommand" }( "Execute subcommand" ) + | Arg{ subArg, "arg1" }( "Arg1" ).required() + | Opt{ subOpt }["--opt"]( "Opt" ) + , Cmd{ subcommand, "internal" }( "Execute another subcommand" ).hidden() + ); + + REQUIRE( subcommand == "" ); + + SECTION( "subcommand.1" ) { + auto result = cli.parse( { "TestApp", "subcommand", "a1" } ); + CHECK( result ); + CHECK( result.value().type() == ParseResultType::Matched ); + CHECK( !showHelp ); + CHECK_THAT( subcommand, Equals( "subcommand" ) ); + CHECK_THAT( subArg, Equals( "a1" ) ); + CHECK( !subOpt); + } + SECTION( "subcommand.2" ) { + auto result = cli.parse( { "TestApp", "subcommand", "a1", "--opt" } ); + CHECK( result ); + CHECK( result.value().type() == ParseResultType::Matched ); + CHECK( !showHelp ); + CHECK_THAT( subcommand, Equals( "subcommand" ) ); + CHECK_THAT( subArg, Equals( "a1" ) ); + CHECK( subOpt ); + } + SECTION( "hidden subcommand" ) { + auto result = cli.parse( { "TestApp", "internal" } ); + CHECK( result ); + CHECK( result.value().type() == ParseResultType::Matched ); + CHECK( !showHelp ); + CHECK_THAT( subcommand, Equals( "internal" ) ); + CHECK_THAT( subArg, Equals( "" ) ); + CHECK( !subOpt ); + } + SECTION( "unmatched subcommand" ) { + auto result = cli.parse( { "TestApp", "xyz" } ); + CHECK( !result ); + CHECK_THAT( result.errorMessage(), Contains( "Unrecognised token" ) && Contains( "xyz" ) ); + CHECK( !showHelp ); + CHECK_THAT( subcommand, Equals( "" ) ); + CHECK_THAT( subArg, Equals( "" ) ); + CHECK( !subOpt ); + } + SECTION( "app version" ) { + auto result = cli.parse( { "TestApp", "-h" } ); + CHECK( result ); + CHECK( showHelp ); + CHECK_THAT( subcommand, Equals( "" ) ); + CHECK_THAT( subArg, Equals( "" ) ); + CHECK( !subOpt); + } + SECTION( "app usage" ) { + std::ostringstream oss; + oss << cli; + auto usage = oss.str(); + REQUIRE(usage == + R"(usage: + options | subcommand + +where options are: + -?, -h, --help display usage information + +where subcommands are: + subcommand Execute subcommand +)" + ); + } + SECTION( "subcommand usage" ) { + std::cout << *cli.findCmd( "subcommand" ); + std::ostringstream oss; + oss << *cli.findCmd( "subcommand" ); + auto usage = oss.str(); + REQUIRE(usage == + R"(Execute subcommand + +usage: + subcommand options + +where arguments are: + Arg1 + +where options are: + --opt Opt +)" +); + } +} From 72880e902d509767d9503dc0cfaa55ebd878d9d7 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 16 Nov 2017 20:36:34 +0100 Subject: [PATCH 08/16] Adjust usage layouting * Lay out subcommand usage in its own line * Optionally allude subcommand to get displayed in main usage * Use 'hinting' angle brackets for "options" and "subcommand" usage --- include/clara.hpp | 59 +++++++++++++++++++++++++++++----------------- src/ClaraTests.cpp | 15 ++++++++---- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/include/clara.hpp b/include/clara.hpp index a902de0..60b6d1c 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -781,6 +781,7 @@ namespace detail { mutable ExeName m_exeName; bool m_isSubcmd = false; + bool m_alludeInUsage = false; std::vector m_cmds; std::vector m_options; std::vector m_args; @@ -829,29 +830,38 @@ namespace detail { os << m_exeName.description() << std::endl << std::endl; } if (!m_exeName.name().empty()) { - os << "usage:\n" << " " << m_exeName.name() << " "; - bool required = true, first = true; - for( auto const &arg : m_args ) { - if (first) - first = false; - else + auto streamArgsAndOpts = [this, &os]( const Parser& p ) { + bool required = true, first = true; + for( auto const &arg : p.m_args ) { os << " "; - if( arg.isOptional() && required ) { - os << "["; - required = false; + if( arg.isOptional() && required ) { + os << "["; + required = false; + } + os << "<" << arg.hint() << ">"; + if( arg.cardinality() == 0 ) + os << " ... "; } - os << "<" << arg.hint() << ">"; - if( arg.cardinality() == 0 ) - os << " ... "; - } - if( !required ) - os << "]"; - if( !m_options.empty() ) - os << " options"; + if( !required ) + os << "]"; + if( !p.m_options.empty() ) + os << " "; + }; + + os << "usage:\n" << " " << m_exeName.name(); + streamArgsAndOpts( *this ); if( !m_cmds.empty() ) { - if( !m_options.empty() ) - os << " |"; - os << " subcommand"; + os << "\n " << m_exeName.name(); + os << " "; + for( const Parser& sub : m_cmds ) + { + if( sub.m_alludeInUsage ) + { + os << "\n " << m_exeName.name(); + os << " " << sub.m_exeName.name(); + streamArgsAndOpts( sub ); + } + } } os << "\n"; } @@ -1009,12 +1019,17 @@ namespace detail { auto hidden() -> Cmd& { m_hidden = true; return *this; - }; + } + + auto alludeInUsage() -> Cmd& { + m_alludeInUsage = true; + return *this; + } auto operator()( std::string description ) -> Cmd& { m_exeName.description( move( description ) ); return *this; - }; + } }; template diff --git a/src/ClaraTests.cpp b/src/ClaraTests.cpp index 93dba43..04ae7fa 100644 --- a/src/ClaraTests.cpp +++ b/src/ClaraTests.cpp @@ -107,7 +107,7 @@ TEST_CASE( "Combined parser" ) { auto usage = oss.str(); REQUIRE(usage == "usage:\n" - " [ ... ] options\n" + " [ ... ] \n" "\n" "where options are:\n" " -?, -h, --help display usage information\n" @@ -282,7 +282,7 @@ TEST_CASE( "cmdline" ) { auto usage = oss.str(); REQUIRE(usage == "usage:\n" - " [ ] options\n" + " [ ] \n" "\n" "where options are:\n" " -o, --output specifies output file\n" @@ -350,6 +350,9 @@ TEST_CASE( "Subcommands" ) { , Cmd{ subcommand, "subcommand" }( "Execute subcommand" ) | Arg{ subArg, "arg1" }( "Arg1" ).required() | Opt{ subOpt }["--opt"]( "Opt" ) + , Cmd{ subcommand, "important" }( "Execute important subcommand" ).alludeInUsage() + | Arg{ subArg, "arg1" }( "Arg1" ).required() + | Opt{ subOpt }["--opt"]( "Opt" ) , Cmd{ subcommand, "internal" }( "Execute another subcommand" ).hidden() ); @@ -405,18 +408,20 @@ TEST_CASE( "Subcommands" ) { auto usage = oss.str(); REQUIRE(usage == R"(usage: - options | subcommand + + + important where options are: -?, -h, --help display usage information where subcommands are: subcommand Execute subcommand + important Execute important subcommand )" ); } SECTION( "subcommand usage" ) { - std::cout << *cli.findCmd( "subcommand" ); std::ostringstream oss; oss << *cli.findCmd( "subcommand" ); auto usage = oss.str(); @@ -424,7 +429,7 @@ where subcommands are: R"(Execute subcommand usage: - subcommand options + subcommand where arguments are: Arg1 From a50acb88e16f7e886a2c1fef1a03ba5f11cc216b Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 16 Nov 2017 20:54:16 +0100 Subject: [PATCH 09/16] Amend #d31850a --- include/clara.hpp | 2 +- src/ClaraTests.cpp | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/include/clara.hpp b/include/clara.hpp index 81341e3..6db5edf 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -626,7 +626,7 @@ namespace detail { return {}; else { std::ostringstream oss; - oss << " <" << m_hint << ">"; + oss << "<" << m_hint << ">"; return { { oss.str(), m_description} }; } } diff --git a/src/ClaraTests.cpp b/src/ClaraTests.cpp index 738e84d..f9f82a5 100644 --- a/src/ClaraTests.cpp +++ b/src/ClaraTests.cpp @@ -109,6 +109,9 @@ TEST_CASE( "Combined parser" ) { "usage:\n" " [ ... ] options\n" "\n" + "where arguments are:\n" + " which test or tests to use\n" + "\n" "where options are:\n" " -?, -h, --help display usage information\n" " --rng-seed, -r set a specific seed for random numbers\n" @@ -175,8 +178,7 @@ struct TestOpt { ( "A flag" ) | Arg( firstPos, "first arg" ) ( "First position" ) - | Arg( secondPos, "second arg" ) - ( "Second position" ); + | Arg( secondPos, "second arg" ); } }; @@ -284,6 +286,9 @@ TEST_CASE( "cmdline" ) { "usage:\n" " [ ] options\n" "\n" + "where arguments are:\n" + " First position\n" + "\n" "where options are:\n" " -o, --output specifies output file\n" " -n \n" From 52f38b456411260fc12b3338122a8161cadc235d Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 16 Nov 2017 21:06:55 +0100 Subject: [PATCH 10/16] Capitalize usage information --- include/clara.hpp | 8 ++++---- src/ClaraTests.cpp | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/include/clara.hpp b/include/clara.hpp index 849e17b..291e11f 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -848,7 +848,7 @@ namespace detail { os << " "; }; - os << "usage:\n" << " " << m_exeName.name(); + os << "Usage:\n" << " " << m_exeName.name(); streamArgsAndOpts( *this ); if( !m_cmds.empty() ) { os << "\n " << m_exeName.name(); @@ -885,8 +885,8 @@ namespace detail { } }; - streamHelpColumns( getHelpColumns( m_args ), "\nwhere arguments are:" ); - streamHelpColumns( getHelpColumns( m_options ), "\nwhere options are:" ); + streamHelpColumns( getHelpColumns( m_args ), "\nWhere arguments are:" ); + streamHelpColumns( getHelpColumns( m_options ), "\nWhere options are:" ); if( !m_cmds.empty() ) { std::vector cmdCols; @@ -901,7 +901,7 @@ namespace detail { } } - streamHelpColumns(cmdCols, "\nwhere subcommands are:"); + streamHelpColumns(cmdCols, "\nWhere subcommands are:"); } } diff --git a/src/ClaraTests.cpp b/src/ClaraTests.cpp index e1d0f89..cecef01 100644 --- a/src/ClaraTests.cpp +++ b/src/ClaraTests.cpp @@ -106,13 +106,13 @@ TEST_CASE( "Combined parser" ) { oss << parser; auto usage = oss.str(); REQUIRE(usage == - "usage:\n" + "Usage:\n" " [ ... ] \n" "\n" - "where arguments are:\n" + "Where arguments are:\n" " which test or tests to use\n" "\n" - "where options are:\n" + "Where options are:\n" " -?, -h, --help display usage information\n" " --rng-seed, -r set a specific seed for random numbers\n" " -n, --name the name to use\n" @@ -283,13 +283,13 @@ TEST_CASE( "cmdline" ) { oss << cli; auto usage = oss.str(); REQUIRE(usage == - "usage:\n" + "Usage:\n" " [ ] \n" "\n" - "where arguments are:\n" + "Where arguments are:\n" " First position\n" "\n" - "where options are:\n" + "Where options are:\n" " -o, --output specifies output file\n" " -n \n" " -i An index, which is an integer between 0 and 10,\n" @@ -412,15 +412,15 @@ TEST_CASE( "Subcommands" ) { oss << cli; auto usage = oss.str(); REQUIRE(usage == - R"(usage: + R"(Usage: important -where options are: +Where options are: -?, -h, --help display usage information -where subcommands are: +Where subcommands are: subcommand Execute subcommand important Execute important subcommand )" @@ -433,13 +433,13 @@ where subcommands are: REQUIRE(usage == R"(Execute subcommand -usage: +Usage: subcommand -where arguments are: +Where arguments are: Arg1 -where options are: +Where options are: --opt Opt )" ); From 3bbc033159f4a81130d9673ef3387203e5213201 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 28 Dec 2017 22:41:22 +0100 Subject: [PATCH 11/16] Correct qualified dependent-name calls to base methods Those calls from a non-template class derived from a template class aren't necessary to fix, but I did for consistency --- include/clara.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/clara.hpp b/include/clara.hpp index 291e11f..ed94515 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -563,7 +563,7 @@ namespace detail { if( !isOptional() && count() < 1 ) return Result::runtimeError( "Missing token: " + hint() ); m_count = 0; - return ComposableParserImpl::validateFinal(); + return ComposableParserImpl::validateFinal(); } auto canParse() const -> bool override { @@ -625,7 +625,7 @@ namespace detail { class Arg : public ParserRefImpl { public: - using ParserRefImpl::ParserRefImpl; + using ParserRefImpl::ParserRefImpl; auto getHelpColumns() const -> std::vector { if( m_description.empty() || m_hidden ) @@ -668,15 +668,15 @@ namespace detail { public: template - explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {} + explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {} - explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {} + explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {} template - Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} template - Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} auto operator[]( std::string const &optName ) -> Opt & { m_optNames.push_back( optName ); @@ -758,7 +758,7 @@ namespace detail { return Result::logicError( "Option name must begin with '-'" ); #endif } - return ParserRefImpl::validateSettings(); + return ParserRefImpl::validateSettings(); } }; From 37f91046154d2c58db74980e264c8f6998bfef9f Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 12 Nov 2019 12:45:33 +0200 Subject: [PATCH 12/16] #29 Fix template lookup (Amend #d31850a) --- include/clara.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/clara.hpp b/include/clara.hpp index 6db5edf..792bfb5 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -544,7 +544,7 @@ namespace detail { }; auto hidden() -> DerivedT & { - m_hidden = true; + this->m_hidden = true; return static_cast(*this); }; From ac4a60192e78e4dddfab3128d6964b89fccbb3a1 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 3 Dec 2023 13:34:23 +0200 Subject: [PATCH 13/16] Alluding in usage help output depends on runtime flag --- include/clara.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/clara.hpp b/include/clara.hpp index 371c1d4..ff48507 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -1021,8 +1021,8 @@ namespace detail { return *this; } - auto alludeInUsage() -> Cmd& { - m_alludeInUsage = true; + auto alludeInUsage(bool yes = true) -> Cmd& { + m_alludeInUsage = yes; return *this; } From c58c40219e9f495e692ac05cc0b09b6b047428a9 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 3 Dec 2023 13:35:43 +0200 Subject: [PATCH 14/16] Clearer error when parsing a subcommand fails --- include/clara.hpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/include/clara.hpp b/include/clara.hpp index ff48507..e4fec00 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -100,9 +100,7 @@ namespace detail { void loadBuffer() { m_tokenBuffer.resize( 0 ); - // Skip any empty strings - while( it != itEnd && it->empty() ) - ++it; + // Note: not skiping empty strings if( it != itEnd ) { auto const &next = *it; @@ -962,10 +960,13 @@ namespace detail { for( auto const& opt : m_options ) parsers[i++] = &opt; for( auto const& arg : m_args ) parsers[i++] = &arg; - if( m_isSubcmd ) - m_exeName.set( tokens->token ); - else - m_exeName.set( exeName ); + if (m_isSubcmd) { + if (auto result = m_exeName.set(tokens->token); !result) + return InternalParseResult(result); + } + else { + m_exeName.set(exeName); + } auto result = InternalParseResult::ok( m_isSubcmd ? ParseState( ParseResultType::Matched, ++TokenStream( tokens ) ) : From 9d59cec00c774eb05d810033822dee810ed2e669 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 29 Jan 2024 20:41:11 +0200 Subject: [PATCH 15/16] Amended merging upstream/master into subcommand Amended #c66b20247858 --- include/clara.hpp | 8 +++----- single_include/clara.hpp | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/include/clara.hpp b/include/clara.hpp index c78bf09..c6ca12e 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -116,7 +116,7 @@ namespace detail { void loadBuffer() { m_tokenBuffer.resize( 0 ); - // Note: not skiping empty strings + // Note: not skipping empty strings if( it != itEnd ) { auto const &next = *it; @@ -979,9 +979,6 @@ namespace detail { } } - struct ParserInfo { - ParserBase const* parser = nullptr; - }; const size_t totalParsers = m_options.size() + m_args.size(); assert( totalParsers < 512 ); // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do @@ -1007,7 +1004,8 @@ namespace detail { while( result.value().remainingTokens() ) { bool tokenParsed = false; - for (auto& parser : parsers) { + for (size_t i = 0; i < totalParsers; ++i) { + ParserBase const* parser = parsers[i]; if( parser->canParse() ) { result = parser->internalParse(exeName, result.value().remainingTokens()); if (!result) diff --git a/single_include/clara.hpp b/single_include/clara.hpp index 84eb21f..2beaad4 100644 --- a/single_include/clara.hpp +++ b/single_include/clara.hpp @@ -456,7 +456,7 @@ namespace detail { void loadBuffer() { m_tokenBuffer.resize( 0 ); - // Note: not skiping empty strings + // Note: not skipping empty strings if( it != itEnd ) { auto const &next = *it; @@ -1319,9 +1319,6 @@ namespace detail { } } - struct ParserInfo { - ParserBase const* parser = nullptr; - }; const size_t totalParsers = m_options.size() + m_args.size(); assert( totalParsers < 512 ); // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do @@ -1347,7 +1344,8 @@ namespace detail { while( result.value().remainingTokens() ) { bool tokenParsed = false; - for (auto& parser : parsers) { + for (size_t i = 0; i < totalParsers; ++i) { + ParserBase const* parser = parsers[i]; if( parser->canParse() ) { result = parser->internalParse(exeName, result.value().remainingTokens()); if (!result) From 19101babbd66810c17c767e741ee41f6c366ac26 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 9 Jan 2026 11:41:02 +0100 Subject: [PATCH 16/16] Omit `` in help output if all options are hidden --- include/clara.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/clara.hpp b/include/clara.hpp index c6ca12e..979e9b4 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -497,6 +497,8 @@ namespace detail { auto parse( Args const &args ) const -> InternalParseResult { return parse( args.exeName(), TokenStream( args ) ); } + + bool isHidden() const { return m_hidden; } }; template @@ -865,7 +867,7 @@ namespace detail { } if( !required ) os << "]"; - if( !p.m_options.empty() ) + if( !p.m_options.empty() && !std::all_of(p.m_options.begin(), p.m_options.end(), [](const Opt& opt) { return opt.isHidden(); }) ) os << " "; };