More CmdLine work

- Support for non string values
- Support for chaining parsers
This commit is contained in:
Phil Nash 2013-04-29 19:26:18 +01:00
parent 26ae11774b
commit 46846a47f3

View File

@ -10,7 +10,6 @@
#endif #endif
#include "catch.hpp" #include "catch.hpp"
//#include "catch_ptr.hpp"
namespace Catch { namespace Catch {
@ -51,6 +50,18 @@ protected:
int m_weight; int m_weight;
}; };
template<typename T>
bool convertInto( std::string const& _source, T& _dest ) {
std::stringstream ss;
ss << _source;
ss >> _dest;
return !ss.fail();
}
inline bool convertInto( std::string const& _source, std::string& _dest ) {
_dest = _source;
return true;
}
template<typename T> template<typename T>
class Opt { class Opt {
public: public:
@ -87,14 +98,12 @@ private:
virtual ~IField() {} virtual ~IField() {}
virtual bool parseInto( std::string const& _arg, T& _config ) const = 0; virtual bool parseInto( std::string const& _arg, T& _config ) const = 0;
}; };
template<typename M> template<typename M>
struct Field : IField { struct Field : IField {
Field( M const& _member ) : member( _member ) {} Field( M const& _member ) : member( _member ) {}
bool parseInto( std::string const& _arg, T& _config ) const { bool parseInto( std::string const& _arg, T& _config ) const {
std::stringstream ss; return convertInto( _arg, _config.*member );
ss << _arg;
ss >> _config.*member;
return !ss.fail();
} }
M member; M member;
}; };
@ -146,40 +155,58 @@ public:
if( !_opt.longOpt().empty() ) if( !_opt.longOpt().empty() )
m_optionsByName.insert( std::make_pair( "--" + _opt.longOpt(), &m_allOptionParsers.back() ) ); m_optionsByName.insert( std::make_pair( "--" + _opt.longOpt(), &m_allOptionParsers.back() ) );
} }
bool parseArgs( int argc, const char* const argv[], T& config ) {
for( int i = 0; i < argc; ++i ) { void parseArgs( int argc, const char* const argv[], T& _config ) {
std::string fullArg = argv[i]; for( int i = 0; i < argc; ++i )
if( fullArg[0] == '-' ) { parseArg( argv[i], _config );
std::string args, optName; }
std::size_t pos = fullArg.find( ':' );
if( pos == std::string::npos ) { void parseArgs( std::vector<std::string> const& _args, T& _config ) {
optName = fullArg; for( std::vector<std::string>::const_iterator
} it = _args.begin(), itEnd = _args.end();
else { it != itEnd;
optName = fullArg.substr(0, pos ); ++it )
args = fullArg.substr( pos+1 ); parseArg( *it, _config );
} }
typename std::map<std::string, Opt<T>*>::const_iterator it = m_optionsByName.find( optName );
bool used = false; template<typename U>
if( it != m_optionsByName.end() ) { void parseRemainingArgs( CommandLineParser<U>& _parser, T& _config ) {
try { parseArgs( _parser.m_unusedOpts, _config );
used = it->second->parseInto( args, config ); }
}
catch( std::exception& ex ) { void parseArg( std::string const& _arg, T& _config ) {
throw std::domain_error( "Error in " + optName + " option: " + ex.what() ); if( _arg[0] == '-' ) {
} std::string args, optName;
} std::size_t pos = _arg.find( ':' );
if( !used ) if( pos == std::string::npos ) {
m_unusedOpts.push_back( fullArg ); optName = _arg;
} }
else { else {
m_args.push_back( fullArg ); optName = _arg.substr(0, pos );
args = _arg.substr( pos+1 );
} }
typename std::map<std::string, Opt<T>*>::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 );
} }
return false;
} }
private: private:
template<typename U>
friend class CommandLineParser;
std::vector<Opt<T> > m_allOptionParsers; std::vector<Opt<T> > m_allOptionParsers;
std::map<std::string, Opt<T>*> m_optionsByName; std::map<std::string, Opt<T>*> m_optionsByName;
std::vector<std::string> m_args; std::vector<std::string> m_args;
@ -197,6 +224,10 @@ struct TestOpt {
int number; int number;
}; };
struct TestOpt2 {
std::string description;
};
TEST_CASE( "Arg" ) { TEST_CASE( "Arg" ) {
SECTION( "pre and post" ) { SECTION( "pre and post" ) {
Catch::ArgData preAndPost( "prefix<arg>postfix" ); Catch::ArgData preAndPost( "prefix<arg>postfix" );
@ -291,11 +322,9 @@ TEST_CASE( "cmdline", "" ) {
CHECK( config.streamName == "stdout" ); CHECK( config.streamName == "stdout" );
} }
Catch::Opt<TestOpt> opt2( "a number" ); parser.addOption( Catch::Opt<TestOpt>( "a number" )
opt2.shortOpt( "n" ) .shortOpt( "n" )
.addArg( "<an integral value>", &TestOpt::number ); .addArg( "<an integral value>", &TestOpt::number ) );
parser.addOption( opt2 );
SECTION( "a number" ) { SECTION( "a number" ) {
const char* argv[] = { "test", "-n:42" }; const char* argv[] = { "test", "-n:42" };
@ -310,4 +339,24 @@ TEST_CASE( "cmdline", "" ) {
CHECK( config.number == 0 ); CHECK( config.number == 0 );
} }
SECTION( "two parsers" ) {
TestOpt config1;
TestOpt2 config2;
Catch::CommandLineParser<TestOpt2> parser2;
parser2.addOption( Catch::Opt<TestOpt2>( "description" )
.shortOpt( "d" )
.longOpt( "description" )
.addArg( "<some text>", &TestOpt2::description ) );
const char* argv[] = { "test", "-n:42", "-d:some text" };
parser.parseArgs( sizeof(argv)/sizeof(char*), argv, config1 );
CHECK( config1.number == 42 );
parser2.parseRemainingArgs( parser, config2 );
CHECK( config2.description == "some text" );
}
} }