diff --git a/projects/SelfTest/CmdLineTests.cpp b/projects/SelfTest/CmdLineTests.cpp index 6689b60c..1c65552a 100644 --- a/projects/SelfTest/CmdLineTests.cpp +++ b/projects/SelfTest/CmdLineTests.cpp @@ -235,43 +235,8 @@ namespace Clara { template class CommandLine { - class ArgBinder { - public: - ArgBinder( CommandLine* cl ) : m_cl( cl ) {} - - template - ArgBinder& bind( F f ) { - if( !m_cl->m_args.empty() ) - m_cl->m_args.back().validate(); - m_cl->m_args.push_back( Arg( Detail::makeBoundField( f ) ) ); - return *this; - } - ArgBinder& shortOpt( std::string const& name ) { - m_cl->m_args.back().shortNames.push_back( name ); - return *this; - } - ArgBinder& longOpt( std::string const& name ) { - m_cl->m_args.back().longName = name; - return *this; - } - ArgBinder& describe( std::string const& description ) { - m_cl->m_args.back().description = description; - return *this; - } - ArgBinder& argName( std::string const& argName ) { - m_cl->m_args.back().argName = argName; - return *this; - } - ArgBinder& position( int /*position*/ ) { - // !TBD: Support for positional args in fixed positions - return *this; - } - private: - CommandLine* m_cl; - }; - struct Arg { - Arg( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} + Arg( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ), position( -1 ) {} bool hasShortName( std::string const& shortName ) const { for( std::vector::const_iterator @@ -288,8 +253,11 @@ namespace Clara { bool takesArg() const { return !argName.empty(); } - bool isPositional() const { - return shortNames.empty() && longName.empty(); + bool isFixedPositional() const { + return position != -1; + } + bool isAnyPositional() const { + return position == -1 && shortNames.empty() && longName.empty(); } std::string dbgName() const { if( !longName.empty() ) @@ -328,18 +296,70 @@ namespace Clara { std::string longName; std::string description; std::string argName; + int position; + }; + + class ArgBinder { + public: + template + ArgBinder( CommandLine* cl, F f ) + : m_cl( cl ), + m_arg( Detail::makeBoundField( f ) ) + {} + ArgBinder( ArgBinder& other ) + : m_cl( other.m_cl ), + m_arg( other.m_arg ) + { + other.m_cl = NULL; + } + ~ArgBinder() { + if( m_cl ) { + m_arg.validate(); + if( m_arg.isFixedPositional() ) + m_cl->m_positionalArgs.push_back( m_arg ); + else if( m_arg.isAnyPositional() ) { + if( m_cl->m_arg.get() ) + throw std::logic_error( "Only one unpositional argument can be added" ); + m_cl->m_arg = std::auto_ptr( new Arg( m_arg ) ); + } + else + m_cl->m_options.push_back( m_arg ); + } + } + ArgBinder& shortOpt( std::string const& name ) { + m_arg.shortNames.push_back( name ); + return *this; + } + ArgBinder& longOpt( std::string const& name ) { + m_arg.longName = name; + return *this; + } + ArgBinder& describe( std::string const& description ) { + m_arg.description = description; + return *this; + } + ArgBinder& argName( std::string const& argName ) { + m_arg.argName = argName; + return *this; + } + ArgBinder& position( int position ) { + m_arg.position = position; + return *this; + } + private: + CommandLine* m_cl; + Arg m_arg; }; public: template ArgBinder bind( F f ) { - ArgBinder binder( this ); - binder.bind( f ); + ArgBinder binder( this, f ); return binder; } void usage( std::ostream& os ) const { - typename std::vector::const_iterator itBegin = m_args.begin(), itEnd = m_args.end(), it; + typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; std::size_t maxWidth = 0; for( it = itBegin; it != itEnd; ++it ) maxWidth = (std::max)( maxWidth, it->commands().size() ); @@ -378,14 +398,20 @@ namespace Clara { } std::vector populate( std::vector const& tokens, ConfigT& config ) const { - if( m_args.empty() ) + if( m_options.empty() && m_positionalArgs.empty() ) throw std::logic_error( "No options or arguments specified" ); - m_args.back().validate(); + std::vector unusedTokens = populateOptions( tokens, config ); + unusedTokens = populateFixedArgs( unusedTokens, config ); + unusedTokens = populateFloatingArgs( unusedTokens, config ); + return unusedTokens; + } + + std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { std::vector unusedTokens; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; - typename std::vector::const_iterator it = m_args.begin(), itEnd = m_args.end(); + typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); for(; it != itEnd; ++it ) { Arg const& arg = *it; @@ -401,19 +427,50 @@ namespace Clara { } break; } - else if( token.type == Parser::Token::Positional && arg.isPositional() ) { - arg.boundField.set( config, token.data ); - break; - } } if( it == itEnd ) unusedTokens.push_back( token ); } return unusedTokens; } + std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + int position = 1; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::vector::const_iterator it = m_positionalArgs.begin(), itEnd = m_positionalArgs.end(); + for(; it != itEnd; ++it ) { + Arg const& arg = *it; + if( token.type == Parser::Token::Positional ) + if( arg.position == position ) { + position++; + arg.boundField.set( config, token.data ); + break; + } + } + if( it == itEnd ) + unusedTokens.push_back( token ); + } + return unusedTokens; + } + std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { + if( !m_arg.get() ) + return tokens; + std::vector unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + if( token.type == Parser::Token::Positional ) + m_arg->boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + } + return unusedTokens; + } private: - std::vector m_args; + std::vector m_options; + std::vector m_positionalArgs; + std::auto_ptr m_arg; }; } // end namespace Clara @@ -432,6 +489,9 @@ struct TestOpt { int number; int index; bool flag; + std::string firstPos; + std::string secondPos; + std::string unpositional; void setValidIndex( int i ) { if( i < 0 || i > 10 ) @@ -557,6 +617,26 @@ TEST_CASE( "cmdline" ) { REQUIRE( config.flag == false ); } } + SECTION( "positional" ) { + cli.bind( &TestOpt::secondPos ) + .describe( "Second position" ) + .argName( "second arg" ) + .position( 2 ); + cli.bind( &TestOpt::unpositional ) + .argName( "any arg" ) + .describe( "Unpositional" ); + cli.bind( &TestOpt::firstPos ) + .describe( "First position" ) + .argName( "first arg" ) + .position( 1 ); + + const char* argv[] = { "test", "-f", "1st", "-o", "filename", "2nd", "3rd" }; + parseInto( cli, argv, config ); + + REQUIRE( config.firstPos == "1st" ); + REQUIRE( config.secondPos == "2nd" ); + REQUIRE( config.unpositional == "3rd" ); + } } struct Config {