mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-11-04 05:59:32 +01:00 
			
		
		
		
	Clara: Allow space separator, as well as : and =
This commit is contained in:
		@@ -84,6 +84,8 @@ public:
 | 
				
			|||||||
        return *this;
 | 
					        return *this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    std::size_t takesArg() const { return !m_args.empty(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    std::string synopsis() const { return m_synopsis; }
 | 
					    std::string synopsis() const { return m_synopsis; }
 | 
				
			||||||
    std::string shortOpt() const { return m_shortOpt; }
 | 
					    std::string shortOpt() const { return m_shortOpt; }
 | 
				
			||||||
    std::string longOpt() const { return m_longOpt; }
 | 
					    std::string longOpt() const { return m_longOpt; }
 | 
				
			||||||
@@ -114,7 +116,7 @@ public:
 | 
				
			|||||||
        if( !_opt.m_longOpt.empty() )
 | 
					        if( !_opt.m_longOpt.empty() )
 | 
				
			||||||
            os << "--" << _opt.m_longOpt;
 | 
					            os << "--" << _opt.m_longOpt;
 | 
				
			||||||
        if( !_opt.m_args.empty() ) {
 | 
					        if( !_opt.m_args.empty() ) {
 | 
				
			||||||
            os << " : ";
 | 
					            os << " ";
 | 
				
			||||||
            typename std::vector<Arg>::const_iterator
 | 
					            typename std::vector<Arg>::const_iterator
 | 
				
			||||||
                it = _opt.m_args.begin(),
 | 
					                it = _opt.m_args.begin(),
 | 
				
			||||||
                itEnd = _opt.m_args.end();
 | 
					                itEnd = _opt.m_args.end();
 | 
				
			||||||
@@ -199,22 +201,23 @@ template<typename T>
 | 
				
			|||||||
class Parser
 | 
					class Parser
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
 | 
					    Parser()
 | 
				
			||||||
 | 
					    :   m_separatorChars( "=: " ),
 | 
				
			||||||
 | 
					        m_allowSpaceSeparator( m_separatorChars.find( ' ' ) != std::string::npos )
 | 
				
			||||||
 | 
					    {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Opt<T>& addOption( std::string const& _synposis ) {
 | 
					    Opt<T>& addOption( std::string const& _synposis ) {
 | 
				
			||||||
        m_allOptionParsers.push_back( _synposis );
 | 
					        m_allOptionParsers.push_back( _synposis );
 | 
				
			||||||
        return m_allOptionParsers.back();
 | 
					        return m_allOptionParsers.back();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void parseArgs( int argc, const char* const argv[], T& _config ) {
 | 
					    void parseArgs( int argc, const char* const argv[], T& _config ) {
 | 
				
			||||||
 | 
					        std::vector<std::string> args;
 | 
				
			||||||
 | 
					        args.reserve( static_cast<std::size_t>( argc ) );
 | 
				
			||||||
        for( int i = 0; i < argc; ++i )
 | 
					        for( int i = 0; i < argc; ++i )
 | 
				
			||||||
            parseArg( argv[i], _config );
 | 
					            args.push_back( argv[i] );
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
    void parseArgs( std::vector<std::string> const& _args, T& _config ) {
 | 
					        parseArgs( args, _config );
 | 
				
			||||||
        for( std::vector<std::string>::const_iterator
 | 
					 | 
				
			||||||
                it = _args.begin(), itEnd = _args.end();
 | 
					 | 
				
			||||||
                it != itEnd;
 | 
					 | 
				
			||||||
                ++it )
 | 
					 | 
				
			||||||
            parseArg( *it, _config );
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    template<typename U>
 | 
					    template<typename U>
 | 
				
			||||||
@@ -222,34 +225,45 @@ public:
 | 
				
			|||||||
        parseArgs( _parser.m_unusedOpts, _config );
 | 
					        parseArgs( _parser.m_unusedOpts, _config );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void parseArg( std::string const& _arg, T& _config ) {
 | 
					    void parseArgs( std::vector<std::string> const& _args, T& _config ) {
 | 
				
			||||||
        ensureOptions();
 | 
					        ensureOptions();
 | 
				
			||||||
 | 
					        for( std::size_t i = 0; i < _args.size(); ++i ) {
 | 
				
			||||||
        if( _arg[0] == '-' ) {
 | 
					            std::string const& arg = _args[i];
 | 
				
			||||||
            std::string args, optName;
 | 
					            if( arg[0] == '-' ) {
 | 
				
			||||||
            std::size_t pos = _arg.find( ':' );
 | 
					                std::string optArgs, optName;
 | 
				
			||||||
            if( pos == std::string::npos ) {
 | 
					                std::size_t pos = arg.find_first_of( m_separatorChars );
 | 
				
			||||||
                optName = _arg;
 | 
					                if( pos == std::string::npos ) {
 | 
				
			||||||
 | 
					                    optName = arg;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else {
 | 
				
			||||||
 | 
					                    optName = arg.substr(0, pos );
 | 
				
			||||||
 | 
					                    optArgs = arg.substr( pos+1 );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                typename std::map<std::string, Opt<T> const*>::const_iterator it = m_optionsByName.find( optName );
 | 
				
			||||||
 | 
					                bool used = false;
 | 
				
			||||||
 | 
					                if( it != m_optionsByName.end() ) {
 | 
				
			||||||
 | 
					                    Opt<T> const& opt = *(it->second);                    
 | 
				
			||||||
 | 
					                    if( opt.takesArg() ) {
 | 
				
			||||||
 | 
					                        if( optArgs.empty() ) {
 | 
				
			||||||
 | 
					                            if( i < _args.size() && _args[i+1][0] != '-' )
 | 
				
			||||||
 | 
					                                optArgs = _args[++i];
 | 
				
			||||||
 | 
					                            else
 | 
				
			||||||
 | 
					                                throw std::domain_error( "Expected argument"); // !TBD better error
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        used = opt.parseInto( optArgs, _config );
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    catch( std::exception& ex ) {
 | 
				
			||||||
 | 
					                        throw std::domain_error( "Error in " + optName + " option: " + ex.what() );
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if( !used )
 | 
				
			||||||
 | 
					                    m_unusedOpts.push_back( arg );
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else {
 | 
					            else {
 | 
				
			||||||
                optName = _arg.substr(0, pos );
 | 
					                m_args.push_back( arg );
 | 
				
			||||||
                args = _arg.substr( pos+1 );
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            typename std::map<std::string, Opt<T> const*>::const_iterator it = m_optionsByName.find( optName );
 | 
					 | 
				
			||||||
            bool used = false;
 | 
					 | 
				
			||||||
            if( it != m_optionsByName.end() ) {
 | 
					 | 
				
			||||||
                try {
 | 
					 | 
				
			||||||
                    used = it->second->parseInto( args, _config );
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                catch( std::exception& ex ) {
 | 
					 | 
				
			||||||
                    throw std::domain_error( "Error in " + optName + " option: " + ex.what() );
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if( !used )
 | 
					 | 
				
			||||||
                m_unusedOpts.push_back( _arg );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else {
 | 
					 | 
				
			||||||
            m_args.push_back( _arg );
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@@ -297,6 +311,8 @@ private:
 | 
				
			|||||||
    mutable std::map<std::string, Opt<T> const*> m_optionsByName;
 | 
					    mutable std::map<std::string, Opt<T> const*> m_optionsByName;
 | 
				
			||||||
    std::vector<std::string> m_args;
 | 
					    std::vector<std::string> m_args;
 | 
				
			||||||
    std::vector<std::string> m_unusedOpts;
 | 
					    std::vector<std::string> m_unusedOpts;
 | 
				
			||||||
 | 
					    std::string m_separatorChars;
 | 
				
			||||||
 | 
					    bool m_allowSpaceSeparator;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@@ -386,21 +402,35 @@ TEST_CASE( "cmdline", "" ) {
 | 
				
			|||||||
        .addArg( "%<stream name>", &TestOpt::streamName );
 | 
					        .addArg( "%<stream name>", &TestOpt::streamName );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SECTION( "plain filename" ) {
 | 
					    SECTION( "plain filename" ) {
 | 
				
			||||||
 | 
					        const char* argv[] = { "test", "-o filename.ext" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config );
 | 
				
			||||||
 | 
					        CHECK( config.fileName == "filename.ext" );
 | 
				
			||||||
 | 
					        CHECK( config.streamName == "" );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    SECTION( "plain filename with colon" ) {
 | 
				
			||||||
        const char* argv[] = { "test", "-o:filename.ext" };
 | 
					        const char* argv[] = { "test", "-o:filename.ext" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config );
 | 
					        parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config );
 | 
				
			||||||
        CHECK( config.fileName == "filename.ext" );
 | 
					        CHECK( config.fileName == "filename.ext" );
 | 
				
			||||||
        CHECK( config.streamName == "" );
 | 
					        CHECK( config.streamName == "" );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    SECTION( "plain filename with =" ) {
 | 
				
			||||||
 | 
					        const char* argv[] = { "test", "-o=filename.ext" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config );
 | 
				
			||||||
 | 
					        CHECK( config.fileName == "filename.ext" );
 | 
				
			||||||
 | 
					        CHECK( config.streamName == "" );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    SECTION( "stream name" ) {
 | 
					    SECTION( "stream name" ) {
 | 
				
			||||||
        const char* argv[] = { "test", "-o:%stdout" };
 | 
					        const char* argv[] = { "test", "-o %stdout" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config );
 | 
					        parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config );
 | 
				
			||||||
        CHECK( config.fileName == "" );
 | 
					        CHECK( config.fileName == "" );
 | 
				
			||||||
        CHECK( config.streamName == "stdout" );
 | 
					        CHECK( config.streamName == "stdout" );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    SECTION( "long opt" ) {
 | 
					    SECTION( "long opt" ) {
 | 
				
			||||||
        const char* argv[] = { "test", "--output:%stdout" };
 | 
					        const char* argv[] = { "test", "--output %stdout" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config );
 | 
					        parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config );
 | 
				
			||||||
        CHECK( config.fileName == "" );
 | 
					        CHECK( config.fileName == "" );
 | 
				
			||||||
@@ -412,13 +442,13 @@ TEST_CASE( "cmdline", "" ) {
 | 
				
			|||||||
            .addArg( "<an integral value>", &TestOpt::number );
 | 
					            .addArg( "<an integral value>", &TestOpt::number );
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    SECTION( "a number" ) {
 | 
					    SECTION( "a number" ) {
 | 
				
			||||||
        const char* argv[] = { "test", "-n:42" };
 | 
					        const char* argv[] = { "test", "-n 42" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config );
 | 
					        parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config );
 | 
				
			||||||
        CHECK( config.number == 42 );
 | 
					        CHECK( config.number == 42 );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    SECTION( "not a number" ) {
 | 
					    SECTION( "not a number" ) {
 | 
				
			||||||
        const char* argv[] = { "test", "-n:forty-two" };
 | 
					        const char* argv[] = { "test", "-n forty-two" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        CHECK_THROWS( parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config ) );
 | 
					        CHECK_THROWS( parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config ) );
 | 
				
			||||||
        CHECK( config.number == 0 );
 | 
					        CHECK( config.number == 0 );
 | 
				
			||||||
@@ -435,7 +465,7 @@ TEST_CASE( "cmdline", "" ) {
 | 
				
			|||||||
                    .longOpt( "description" )
 | 
					                    .longOpt( "description" )
 | 
				
			||||||
                    .addArg( "<some text>", &TestOpt2::description );
 | 
					                    .addArg( "<some text>", &TestOpt2::description );
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        const char* argv[] = { "test", "-n:42", "-d:some text" };
 | 
					        const char* argv[] = { "test", "-n 42", "-d some text" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config1 );
 | 
					        parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config1 );
 | 
				
			||||||
        CHECK( config1.number == 42 );
 | 
					        CHECK( config1.number == 42 );
 | 
				
			||||||
@@ -451,13 +481,13 @@ TEST_CASE( "cmdline", "" ) {
 | 
				
			|||||||
                .addArg( "<index>", &TestOpt::setValidIndex );
 | 
					                .addArg( "<index>", &TestOpt::setValidIndex );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        SECTION( "in range" ) {
 | 
					        SECTION( "in range" ) {
 | 
				
			||||||
            const char* argv[] = { "test", "-i:3" };
 | 
					            const char* argv[] = { "test", "-i 3" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config );
 | 
					            parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config );
 | 
				
			||||||
            REQUIRE( config.index == 3 );
 | 
					            REQUIRE( config.index == 3 );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        SECTION( "out of range" ) {
 | 
					        SECTION( "out of range" ) {
 | 
				
			||||||
            const char* argv[] = { "test", "-i:42" };
 | 
					            const char* argv[] = { "test", "-i 42" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            REQUIRE_THROWS( parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config ) );
 | 
					            REQUIRE_THROWS( parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config ) );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user