From e2f93b6507e58c09762f2e70b274359519ddcd68 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Fri, 3 May 2013 08:08:46 +0100 Subject: [PATCH] Clara: Allow space separator, as well as : and = --- projects/SelfTest/CmdLineTests.cpp | 112 ++++++++++++++++++----------- 1 file changed, 71 insertions(+), 41 deletions(-) diff --git a/projects/SelfTest/CmdLineTests.cpp b/projects/SelfTest/CmdLineTests.cpp index 781bc9a3..55bf159f 100644 --- a/projects/SelfTest/CmdLineTests.cpp +++ b/projects/SelfTest/CmdLineTests.cpp @@ -83,6 +83,8 @@ public: m_args.push_back( Arg( _name, _member ) ); return *this; } + + std::size_t takesArg() const { return !m_args.empty(); } std::string synopsis() const { return m_synopsis; } std::string shortOpt() const { return m_shortOpt; } @@ -114,7 +116,7 @@ public: if( !_opt.m_longOpt.empty() ) os << "--" << _opt.m_longOpt; if( !_opt.m_args.empty() ) { - os << " : "; + os << " "; typename std::vector::const_iterator it = _opt.m_args.begin(), itEnd = _opt.m_args.end(); @@ -199,22 +201,23 @@ template class Parser { public: + Parser() + : m_separatorChars( "=: " ), + m_allowSpaceSeparator( m_separatorChars.find( ' ' ) != std::string::npos ) + {} + Opt& addOption( std::string const& _synposis ) { m_allOptionParsers.push_back( _synposis ); return m_allOptionParsers.back(); } void parseArgs( int argc, const char* const argv[], T& _config ) { + std::vector args; + args.reserve( static_cast( argc ) ); for( int i = 0; i < argc; ++i ) - parseArg( argv[i], _config ); - } - - void parseArgs( std::vector const& _args, T& _config ) { - for( std::vector::const_iterator - it = _args.begin(), itEnd = _args.end(); - it != itEnd; - ++it ) - parseArg( *it, _config ); + args.push_back( argv[i] ); + + parseArgs( args, _config ); } template @@ -222,34 +225,45 @@ public: parseArgs( _parser.m_unusedOpts, _config ); } - void parseArg( std::string const& _arg, T& _config ) { + void parseArgs( std::vector const& _args, T& _config ) { ensureOptions(); - - if( _arg[0] == '-' ) { - std::string args, optName; - std::size_t pos = _arg.find( ':' ); - if( pos == std::string::npos ) { - optName = _arg; + for( std::size_t i = 0; i < _args.size(); ++i ) { + std::string const& arg = _args[i]; + if( arg[0] == '-' ) { + std::string optArgs, optName; + std::size_t pos = arg.find_first_of( m_separatorChars ); + if( pos == std::string::npos ) { + optName = arg; + } + else { + optName = arg.substr(0, pos ); + optArgs = arg.substr( pos+1 ); + } + typename std::map const*>::const_iterator it = m_optionsByName.find( optName ); + bool used = false; + if( it != m_optionsByName.end() ) { + Opt 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 { - optName = _arg.substr(0, pos ); - args = _arg.substr( pos+1 ); + m_args.push_back( arg ); } - typename std::map 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 const*> m_optionsByName; std::vector m_args; std::vector m_unusedOpts; + std::string m_separatorChars; + bool m_allowSpaceSeparator; }; @@ -386,21 +402,35 @@ TEST_CASE( "cmdline", "" ) { .addArg( "%", &TestOpt::streamName ); 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" }; parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config ); CHECK( config.fileName == "filename.ext" ); 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" ) { - const char* argv[] = { "test", "-o:%stdout" }; + const char* argv[] = { "test", "-o %stdout" }; parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config ); CHECK( config.fileName == "" ); CHECK( config.streamName == "stdout" ); } SECTION( "long opt" ) { - const char* argv[] = { "test", "--output:%stdout" }; + const char* argv[] = { "test", "--output %stdout" }; parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config ); CHECK( config.fileName == "" ); @@ -412,13 +442,13 @@ TEST_CASE( "cmdline", "" ) { .addArg( "", &TestOpt::number ); SECTION( "a number" ) { - const char* argv[] = { "test", "-n:42" }; + const char* argv[] = { "test", "-n 42" }; parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config ); CHECK( config.number == 42 ); } 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( config.number == 0 ); @@ -435,7 +465,7 @@ TEST_CASE( "cmdline", "" ) { .longOpt( "description" ) .addArg( "", &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 ); CHECK( config1.number == 42 ); @@ -451,13 +481,13 @@ TEST_CASE( "cmdline", "" ) { .addArg( "", &TestOpt::setValidIndex ); SECTION( "in range" ) { - const char* argv[] = { "test", "-i:3" }; + const char* argv[] = { "test", "-i 3" }; parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config ); REQUIRE( config.index == 3 ); } 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 ) ); }