diff --git a/include/clara.hpp b/include/clara.hpp index 51be3e3..979e9b4 100644 --- a/include/clara.hpp +++ b/include/clara.hpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #if !defined(CLARA_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) @@ -69,16 +70,20 @@ namespace detail { std::vector m_args; public: - Args( int argc, char const* const* argv ) - : m_exeName(argv[0]), - m_args(argv + 1, argv + argc) {} + // 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 const* const* argv, std::string exeName = std::string{} ) { + bool exeOffArgv = exeName.empty(); + m_exeName = exeOffArgv ? argv[0] : std::move(exeName); + m_args.assign(argv + (exeOffArgv ? 1 : 0), argv + argc); + } Args( std::initializer_list args ) : m_exeName( *args.begin() ), m_args( args.begin()+1, args.end() ) {} - auto exeName() const -> std::string { + auto exeName() const -> std::string const& { return m_exeName; } }; @@ -111,9 +116,7 @@ namespace detail { void loadBuffer() { m_tokenBuffer.resize( 0 ); - // Skip any empty strings - while( it != itEnd && it->empty() ) - ++it; + // Note: not skipping empty strings if( it != itEnd ) { auto const &next = *it; @@ -125,7 +128,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 } ); } @@ -150,7 +153,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() ); @@ -254,7 +257,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: void enforceOk() const override { @@ -465,15 +468,37 @@ namespace detail { struct Parser; class ParserBase { + protected: + bool m_hidden; + public: + ParserBase() : m_hidden( false ) {} 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 canParse() const -> bool { return false; } + virtual auto validateSettings() const -> Result { return Result::ok(); } + virtual auto validateFinal() const -> Result { return Result::ok(); } + 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 ) ); } + + bool isHidden() const { return m_hidden; } }; template @@ -494,20 +519,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 & { @@ -518,29 +549,48 @@ namespace detail { auto optional() -> DerivedT & { m_optionality = Optionality::Optional; return static_cast( *this ); - }; + } auto required() -> DerivedT & { m_optionality = Optionality::Required; return static_cast( *this ); - }; + } + + auto hidden() -> DerivedT & { + this->m_hidden = true; + return static_cast(*this); + } auto isOptional() const -> bool { return m_optionality == Optionality::Optional; } - auto cardinality() const -> size_t override { + virtual auto cardinality() const -> std::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 hint() const -> std::string const& { return m_hint; } + + auto count() const -> std::size_t { return m_count; } }; class ExeName : public ComposableParserImpl { std::shared_ptr m_name; + std::shared_ptr m_description; std::shared_ptr m_ref; template @@ -549,7 +599,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 ); @@ -561,20 +614,22 @@ 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 ) ); } - 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 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 ) - ? newName + ? move( newName ) : 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 ); @@ -583,13 +638,19 @@ namespace detail { class Arg : public ParserRefImpl { public: - using ParserRefImpl::ParserRefImpl; + using ParserRefImpl::ParserRefImpl; - auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { - auto validationResult = validate(); - if( !validationResult ) - return InternalParseResult( validationResult ); + 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; if( token.type != TokenType::Argument ) @@ -601,8 +662,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 ) ); + } } }; @@ -621,15 +684,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 ); @@ -637,6 +700,9 @@ namespace detail { } auto getHelpColumns() const -> std::vector { + if( m_hidden ) + return {}; + std::ostringstream oss; bool first = true; for( auto const &opt : m_optNames ) { @@ -662,15 +728,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( m_ref->isFlag() ) { auto flagRef = static_cast( m_ref.get() ); auto result = flagRef->setFlag( true ); @@ -692,13 +754,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 ) { @@ -712,7 +775,7 @@ namespace detail { return Result::logicError( "Option name must begin with '-'" ); #endif } - return ParserRefImpl::validate(); + return ParserRefImpl::validateSettings(); } }; @@ -734,6 +797,9 @@ namespace detail { struct Parser : ParserBase { mutable ExeName m_exeName; + bool m_isSubcmd = false; + bool m_alludeInUsage = false; + std::vector m_cmds; std::vector m_options; std::vector m_args; @@ -755,6 +821,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; } @@ -769,53 +836,97 @@ namespace detail { template auto operator+( T const &other ) const -> Parser { return operator|( 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; } 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; - 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 << " ... "; + } + if( !required ) + os << "]"; + if( !p.m_options.empty() && !std::all_of(p.m_options.begin(), p.m_options.end(), [](const Opt& opt) { return opt.isHidden(); }) ) + os << " "; + }; + + os << "Usage:\n" << " " << m_exeName.name(); + streamArgsAndOpts( *this ); + if( !m_cmds.empty() ) { + 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 << "<" << arg.hint() << ">"; - if( arg.cardinality() == 0 ) - os << " ... "; } - if( !required ) - os << "]"; - if( !m_options.empty() ) - os << " options"; - os << "\n\nwhere options are:" << std::endl; + os << "\n"; } - auto rows = getHelpColumns(); - size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; - size_t optWidth = 0; - for( auto const &cols : rows ) - optWidth = (std::max)(optWidth, cols.left.size() + 2); + 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 optWidth = 0; + for( auto const &cols : rows ) + optWidth = (std::max)(optWidth, cols.left.size() + 2); + + optWidth = (std::min)(optWidth, consoleWidth/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; + } + } + }; - optWidth = (std::min)(optWidth, consoleWidth/2); + 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')) }); + } + } - 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; + streamHelpColumns(cmdCols, "\nWhere subcommands are:"); } } @@ -824,54 +935,85 @@ 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(); } + 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 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 parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + 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 ); + } + } - struct ParserInfo { - ParserBase const* parser = nullptr; - size_t count = 0; - }; 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 ); + if (m_isSubcmd) { + if (auto result = m_exeName.set(tokens->token); !result) + return InternalParseResult(result); + } + 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; - 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()); + 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) return result; if (result.value().type() != ParseResultType::NoMatch) { tokenParsed = true; - ++parseInfo.count; break; } } @@ -882,7 +1024,6 @@ namespace detail { if( !tokenParsed ) return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); } - // !TBD Check missing required options return result; } }; @@ -892,12 +1033,71 @@ 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 alludeInUsage(bool yes = true) -> Cmd& { + m_alludeInUsage = yes; + 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/scripts/stitch.py b/scripts/stitch.py index a8f7465..96d2b2d 100755 --- a/scripts/stitch.py +++ b/scripts/stitch.py @@ -175,7 +175,7 @@ def handleIfdefCommon( self, line ): if m: self.handleIf( m.group(1) ) else: - print "****** error ***** " + line + print( "****** error ***** " + line ) def handleEndif( self, trailing ): global level @@ -220,8 +220,8 @@ def writeLine( self, line ): o.close() -print "-------------" -print level +print( "-------------" ) +print( level ) #for h in systemHeaders: # print "#include <" + h + ">" #print diff --git a/single_include/clara.hpp b/single_include/clara.hpp index 6be5a98..2beaad4 100644 --- a/single_include/clara.hpp +++ b/single_include/clara.hpp @@ -372,8 +372,10 @@ namespace clara { namespace TextFlow { // ........... back in clara.hpp +#include #include #include +#include #include #if !defined(CLARA_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) @@ -408,16 +410,20 @@ namespace detail { std::vector m_args; public: - Args( int argc, char const* const* argv ) - : m_exeName(argv[0]), - m_args(argv + 1, argv + argc) {} + // 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 const* const* argv, std::string exeName = std::string{} ) { + bool exeOffArgv = exeName.empty(); + m_exeName = exeOffArgv ? argv[0] : std::move(exeName); + m_args.assign(argv + (exeOffArgv ? 1 : 0), argv + argc); + } Args( std::initializer_list args ) : m_exeName( *args.begin() ), m_args( args.begin()+1, args.end() ) {} - auto exeName() const -> std::string { + auto exeName() const -> std::string const& { return m_exeName; } }; @@ -450,9 +456,7 @@ namespace detail { void loadBuffer() { m_tokenBuffer.resize( 0 ); - // Skip any empty strings - while( it != itEnd && it->empty() ) - ++it; + // Note: not skipping empty strings if( it != itEnd ) { auto const &next = *it; @@ -464,7 +468,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 } ); } @@ -489,7 +493,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() ); @@ -593,7 +597,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: void enforceOk() const override { @@ -664,7 +668,7 @@ namespace detail { } inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { std::string srcLC = source; - std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( ::tolower(c) ); } ); + std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( std::tolower(c) ); } ); if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") target = true; else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") @@ -804,11 +808,31 @@ namespace detail { struct Parser; class ParserBase { + protected: + bool m_hidden; + public: + ParserBase() : m_hidden( false ) {} 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 canParse() const -> bool { return false; } + virtual auto validateSettings() const -> Result { return Result::ok(); } + virtual auto validateFinal() const -> Result { return Result::ok(); } + 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 ) ); @@ -833,20 +857,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 & { @@ -857,29 +887,48 @@ namespace detail { auto optional() -> DerivedT & { m_optionality = Optionality::Optional; return static_cast( *this ); - }; + } auto required() -> DerivedT & { m_optionality = Optionality::Required; return static_cast( *this ); - }; + } + + auto hidden() -> DerivedT & { + this->m_hidden = true; + return static_cast(*this); + } auto isOptional() const -> bool { return m_optionality == Optionality::Optional; } - auto cardinality() const -> size_t override { + virtual auto cardinality() const -> std::size_t { if( m_ref->isContainer() ) return 0; else return 1; } - auto hint() const -> std::string { return m_hint; } + 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 const& { return m_hint; } + + auto count() const -> std::size_t { return m_count; } }; class ExeName : public ComposableParserImpl { std::shared_ptr m_name; + std::shared_ptr m_description; std::shared_ptr m_ref; template @@ -888,7 +937,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 ); @@ -900,20 +952,22 @@ 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 ) ); } - 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 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 ) - ? newName + ? move( newName ) : 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 ); @@ -922,13 +976,19 @@ namespace detail { class Arg : public ParserRefImpl { public: - using ParserRefImpl::ParserRefImpl; + using ParserRefImpl::ParserRefImpl; - auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { - auto validationResult = validate(); - if( !validationResult ) - return InternalParseResult( validationResult ); + 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; if( token.type != TokenType::Argument ) @@ -940,8 +1000,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 ) ); + } } }; @@ -960,15 +1022,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 ); @@ -976,6 +1038,9 @@ namespace detail { } auto getHelpColumns() const -> std::vector { + if( m_hidden ) + return {}; + std::ostringstream oss; bool first = true; for( auto const &opt : m_optNames ) { @@ -1001,15 +1066,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( m_ref->isFlag() ) { auto flagRef = static_cast( m_ref.get() ); auto result = flagRef->setFlag( true ); @@ -1031,13 +1092,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 ) { @@ -1051,7 +1113,7 @@ namespace detail { return Result::logicError( "Option name must begin with '-'" ); #endif } - return ParserRefImpl::validate(); + return ParserRefImpl::validateSettings(); } }; @@ -1073,6 +1135,9 @@ namespace detail { struct Parser : ParserBase { mutable ExeName m_exeName; + bool m_isSubcmd = false; + bool m_alludeInUsage = false; + std::vector m_cmds; std::vector m_options; std::vector m_args; @@ -1094,6 +1159,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; } @@ -1108,53 +1174,97 @@ namespace detail { template auto operator+( T const &other ) const -> Parser { return operator|( 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; } 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; - 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 << " ... "; + } + if( !required ) + os << "]"; + if( !p.m_options.empty() ) + os << " "; + }; + + os << "Usage:\n" << " " << m_exeName.name(); + streamArgsAndOpts( *this ); + if( !m_cmds.empty() ) { + 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 << "<" << arg.hint() << ">"; - if( arg.cardinality() == 0 ) - os << " ... "; } - if( !required ) - os << "]"; - if( !m_options.empty() ) - os << " options"; - os << "\n\nwhere options are:" << std::endl; + os << "\n"; } - auto rows = getHelpColumns(); - size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; - size_t optWidth = 0; - for( auto const &cols : rows ) - optWidth = (std::max)(optWidth, cols.left.size() + 2); + 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 optWidth = 0; + for( auto const &cols : rows ) + optWidth = (std::max)(optWidth, cols.left.size() + 2); + + optWidth = (std::min)(optWidth, consoleWidth/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; + } + } + }; - optWidth = (std::min)(optWidth, consoleWidth/2); + 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')) }); + } + } - 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; + streamHelpColumns(cmdCols, "\nWhere subcommands are:"); } } @@ -1163,54 +1273,85 @@ 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(); } + 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 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 parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + 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 ); + } + } - struct ParserInfo { - ParserBase const* parser = nullptr; - size_t count = 0; - }; 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 ); + if (m_isSubcmd) { + if (auto result = m_exeName.set(tokens->token); !result) + return InternalParseResult(result); + } + 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; - 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()); + 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) return result; if (result.value().type() != ParseResultType::NoMatch) { tokenParsed = true; - ++parseInfo.count; break; } } @@ -1221,7 +1362,6 @@ namespace detail { if( !tokenParsed ) return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); } - // !TBD Check missing required options return result; } }; @@ -1231,12 +1371,71 @@ 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 alludeInUsage(bool yes = true) -> Cmd& { + m_alludeInUsage = yes; + 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 1251e9b..262aca7 100644 --- a/src/ClaraTests.cpp +++ b/src/ClaraTests.cpp @@ -1,4 +1,4 @@ -#include "clara.hpp" +#include #include "catch.hpp" @@ -116,10 +116,13 @@ TEST_CASE( "Combined parser" ) { SECTION( "usage" ) { REQUIRE(toString(parser) == - "usage:\n" - " [ ... ] options\n" + "Usage:\n" + " [ ... ] \n" "\n" - "where options are:\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" " -n, --name the name to use\n" @@ -128,7 +131,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 ); @@ -137,6 +140,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 ); @@ -178,8 +188,7 @@ struct TestOpt { ( "A flag" ) | Arg( firstPos, "first arg" ) ( "First position" ) - | Arg( secondPos, "second arg" ) - ( "Second position" ); + | Arg( secondPos, "second arg" ); } }; @@ -303,6 +312,25 @@ TEST_CASE( "cmdline" ) { REQUIRE( config.firstPos == "1st" ); REQUIRE( config.secondPos == "2nd" ); } + SECTION( "usage" ) { + std::ostringstream oss; + oss << cli; + auto usage = oss.str(); + REQUIRE(usage == + "Usage:\n" + " [ ] \n" + "\n" + "Where arguments are:\n" + " First position\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" + ); + } } TEST_CASE( "flag parser" ) { @@ -560,3 +588,106 @@ TEST_CASE("Reading into std::optional") { } } #endif // CLARA_CONFIG_OPTIONAL_TYPE + +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, "important" }( "Execute important subcommand" ).alludeInUsage() + | 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: + + + important + +Where options are: + -?, -h, --help display usage information + +Where subcommands are: + subcommand Execute subcommand + important Execute important subcommand +)" + ); + } + SECTION( "subcommand usage" ) { + std::ostringstream oss; + oss << *cli.findCmd( "subcommand" ); + auto usage = oss.str(); + REQUIRE(usage == + R"(Execute subcommand + +Usage: + subcommand + +Where arguments are: + Arg1 + +Where options are: + --opt Opt +)" +); + } +}