mirror of
https://github.com/catchorg/Catch2.git
synced 2025-05-28 07:29:25 +00:00
Refactored command line parser to be more cohesive
Also added a set of unit tests for them (incomplete)
This commit is contained in:
parent
06e959be3e
commit
163088a11f
12 changed files with 605 additions and 416 deletions
|
@ -12,175 +12,162 @@
|
|||
#include "catch_runner_impl.hpp"
|
||||
|
||||
namespace Catch {
|
||||
// -l, --list tests [xml] lists available tests (optionally in xml)
|
||||
// -l, --list reporters [xml] lists available reports (optionally in xml)
|
||||
// -l, --list all [xml] lists available tests and reports (optionally in xml)
|
||||
// -t, --test "testspec" ["testspec", ...]
|
||||
// -r, --reporter <type>
|
||||
// -o, --out filename to write to
|
||||
// -s, --success report successful cases too
|
||||
// -b, --break breaks into debugger on test failure
|
||||
// -n, --name specifies an optional name for the test run
|
||||
class ArgParser : NonCopyable {
|
||||
|
||||
enum Mode {
|
||||
modeNone,
|
||||
modeList,
|
||||
modeTest,
|
||||
modeReport,
|
||||
modeOutput,
|
||||
modeSuccess,
|
||||
modeBreak,
|
||||
modeName,
|
||||
modeHelp,
|
||||
|
||||
modeError
|
||||
};
|
||||
|
||||
class Command {
|
||||
public:
|
||||
ArgParser ( int argc, char * const argv[], Config& config )
|
||||
: m_mode( modeNone ),
|
||||
m_config( config ) {
|
||||
|
||||
for( int i=1; i < argc; ++i ) {
|
||||
if( argv[i][0] == '-' ) {
|
||||
std::string cmd = ( argv[i] );
|
||||
if( cmd == "-l" || cmd == "--list" )
|
||||
changeMode( cmd, modeList );
|
||||
else if( cmd == "-t" || cmd == "--test" )
|
||||
changeMode( cmd, modeTest );
|
||||
else if( cmd == "-r" || cmd == "--reporter" )
|
||||
changeMode( cmd, modeReport );
|
||||
else if( cmd == "-o" || cmd == "--out" )
|
||||
changeMode( cmd, modeOutput );
|
||||
else if( cmd == "-s" || cmd == "--success" )
|
||||
changeMode( cmd, modeSuccess );
|
||||
else if( cmd == "-b" || cmd == "--break" )
|
||||
changeMode( cmd, modeBreak );
|
||||
else if( cmd == "-n" || cmd == "--name" )
|
||||
changeMode( cmd, modeName );
|
||||
else if( cmd == "-h" || cmd == "-?" || cmd == "--help" )
|
||||
changeMode( cmd, modeHelp );
|
||||
}
|
||||
else {
|
||||
m_args.push_back( argv[i] );
|
||||
}
|
||||
if( m_mode == modeError )
|
||||
return;
|
||||
}
|
||||
changeMode( "", modeNone );
|
||||
Command(){}
|
||||
|
||||
Command( const std::string& name ) : m_name( name ) {}
|
||||
|
||||
Command& operator += ( const std::string& arg ) {
|
||||
m_args.push_back( arg );
|
||||
return *this;
|
||||
}
|
||||
Command& operator += ( const Command& other ) {
|
||||
std::copy( other.m_args.begin(), other.m_args.end(), std::back_inserter( m_args ) );
|
||||
if( m_name.empty() )
|
||||
m_name = other.m_name;
|
||||
return *this;
|
||||
}
|
||||
Command operator + ( const Command& other ) {
|
||||
Command newCommand( *this );
|
||||
newCommand += other;
|
||||
return newCommand;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string argsAsString() {
|
||||
operator SafeBool::type() const {
|
||||
return SafeBool::makeSafe( !m_name.empty() );
|
||||
}
|
||||
|
||||
std::string name() const { return m_name; }
|
||||
std::string operator[]( std::size_t i ) const { return m_args[i]; }
|
||||
std::size_t argsCount() const { return m_args.size(); }
|
||||
|
||||
void raiseError( const std::string& message ) const {
|
||||
std::ostringstream oss;
|
||||
std::vector<std::string>::const_iterator it = m_args.begin();
|
||||
std::vector<std::string>::const_iterator itEnd = m_args.end();
|
||||
for( bool first = true; it != itEnd; ++it, first = false ) {
|
||||
if( !first )
|
||||
oss << " ";
|
||||
oss << *it;
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
void changeMode( const std::string& cmd, Mode mode ) {
|
||||
m_command = cmd;
|
||||
switch( m_mode ) {
|
||||
case modeNone:
|
||||
if( m_args.size() > 0 )
|
||||
return setErrorMode( "Unexpected arguments before " + m_command + ": " + argsAsString() );
|
||||
break;
|
||||
case modeList:
|
||||
if( m_args.size() > 2 ) {
|
||||
return setErrorMode( m_command + " expected upto 2 arguments but recieved: " + argsAsString() );
|
||||
}
|
||||
else {
|
||||
Config::List::What listSpec = Config::List::All;
|
||||
if( m_args.size() >= 1 ) {
|
||||
if( m_args[0] == "tests" )
|
||||
listSpec = Config::List::Tests;
|
||||
else if( m_args[0] == "reporters" )
|
||||
listSpec = Config::List::Reports;
|
||||
else
|
||||
return setErrorMode( m_command + " expected [tests] or [reporters] but recieved: [" + m_args[0] + "]" );
|
||||
}
|
||||
if( m_args.size() >= 2 ) {
|
||||
if( m_args[1] == "xml" )
|
||||
listSpec = static_cast<Config::List::What>( listSpec | Config::List::AsXml );
|
||||
else if( m_args[1] == "text" )
|
||||
listSpec = static_cast<Config::List::What>( listSpec | Config::List::AsText );
|
||||
else
|
||||
return setErrorMode( m_command + " expected [xml] or [text] but recieved: [" + m_args[1] + "]" );
|
||||
}
|
||||
m_config.setListSpec( static_cast<Config::List::What>( m_config.getListSpec() | listSpec ) );
|
||||
}
|
||||
break;
|
||||
case modeTest:
|
||||
if( m_args.size() == 0 )
|
||||
return setErrorMode( m_command + " expected at least 1 argument but recieved none" );
|
||||
{
|
||||
std::vector<std::string>::const_iterator it = m_args.begin();
|
||||
std::vector<std::string>::const_iterator itEnd = m_args.end();
|
||||
for(; it != itEnd; ++it )
|
||||
m_config.addTestSpec( *it );
|
||||
}
|
||||
break;
|
||||
case modeReport:
|
||||
if( m_args.size() != 1 )
|
||||
return setErrorMode( m_command + " expected one argument, recieved: " + argsAsString() );
|
||||
m_config.setReporter( m_args[0] );
|
||||
break;
|
||||
case modeOutput:
|
||||
if( m_args.size() == 0 )
|
||||
return setErrorMode( m_command + " expected filename" );
|
||||
if( m_args[0][0] == '%' )
|
||||
m_config.useStream( m_args[0].substr( 1 ) );
|
||||
else
|
||||
m_config.setFilename( m_args[0] );
|
||||
break;
|
||||
case modeSuccess:
|
||||
if( m_args.size() != 0 )
|
||||
return setErrorMode( m_command + " does not accept arguments" );
|
||||
m_config.setIncludeWhat( Config::Include::SuccessfulResults );
|
||||
break;
|
||||
case modeBreak:
|
||||
if( m_args.size() != 0 )
|
||||
return setErrorMode( m_command + " does not accept arguments" );
|
||||
m_config.setShouldDebugBreak( true );
|
||||
break;
|
||||
case modeName:
|
||||
if( m_args.size() != 1 )
|
||||
return setErrorMode( m_command + " requires exactly one argument (a name)" );
|
||||
m_config.setName( m_args[0] );
|
||||
break;
|
||||
case modeHelp:
|
||||
if( m_args.size() != 0 )
|
||||
return setErrorMode( m_command + " does not accept arguments" );
|
||||
m_config.setShowHelp( true );
|
||||
break;
|
||||
case modeError:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
m_args.clear();
|
||||
m_mode = mode;
|
||||
}
|
||||
|
||||
void setErrorMode( const std::string& errorMessage ) {
|
||||
m_mode = modeError;
|
||||
m_command = "";
|
||||
m_config.setError( errorMessage );
|
||||
oss << "Error while parsing " << m_name << ". " << message << ".";
|
||||
if( m_args.size() > 0 )
|
||||
oss << " Arguments where:";
|
||||
for( std::size_t i = 0; i < m_args.size(); ++i )
|
||||
oss << " " << m_args[i];
|
||||
throw std::domain_error( oss.str() );
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
Mode m_mode;
|
||||
std::string m_command;
|
||||
std::string m_name;
|
||||
std::vector<std::string> m_args;
|
||||
Config& m_config;
|
||||
};
|
||||
|
||||
class CommandParser {
|
||||
public:
|
||||
CommandParser( int argc, char const * const * argv ) : m_argc( argc ), m_argv( argv ) {}
|
||||
|
||||
Command find( const std::string& arg1, const std::string& arg2, const std::string& arg3 ) const {
|
||||
return find( arg1 ) + find( arg2 ) + find( arg3 );
|
||||
}
|
||||
|
||||
Command find( const std::string& shortArg, const std::string& longArg ) const {
|
||||
return find( shortArg ) + find( longArg );
|
||||
}
|
||||
Command find( const std::string& arg ) const {
|
||||
for( std::size_t i = 0; i < m_argc; ++i )
|
||||
if( m_argv[i] == arg )
|
||||
return getArgs( i );
|
||||
return Command();
|
||||
}
|
||||
|
||||
private:
|
||||
Command getArgs( std::size_t from ) const {
|
||||
Command command( m_argv[from] );
|
||||
for( std::size_t i = from+1; i < m_argc && m_argv[i][0] != '-'; ++i )
|
||||
command += m_argv[i];
|
||||
return command;
|
||||
}
|
||||
|
||||
int m_argc;
|
||||
char const * const * m_argv;
|
||||
};
|
||||
|
||||
inline bool parseIntoConfig( const CommandParser& parser, Config& config ) {
|
||||
|
||||
try {
|
||||
if( Command cmd = parser.find( "-l", "--list" ) ) {
|
||||
if( cmd.argsCount() > 2 )
|
||||
throw std::domain_error( cmd.name() + " expected upto 2 arguments but recieved: " );
|
||||
|
||||
List::What listSpec = List::All;
|
||||
if( cmd.argsCount() >= 1 ) {
|
||||
if( cmd[0] == "tests" )
|
||||
listSpec = List::Tests;
|
||||
else if( cmd[0] == "reporters" )
|
||||
listSpec = List::Reports;
|
||||
else
|
||||
throw std::domain_error( cmd.name() + " expected [tests] or [reporters] but recieved: : " );
|
||||
}
|
||||
if( cmd.argsCount() >= 2 ) {
|
||||
if( cmd[1] == "xml" )
|
||||
listSpec = static_cast<List::What>( listSpec | List::AsXml );
|
||||
else if( cmd[1] == "text" )
|
||||
listSpec = static_cast<List::What>( listSpec | List::AsText );
|
||||
else
|
||||
throw std::domain_error( cmd.name() + " expected [xml] or [text] but recieved: " );
|
||||
}
|
||||
config.setListSpec( static_cast<List::What>( config.getListSpec() | listSpec ) );
|
||||
}
|
||||
|
||||
if( Command cmd = parser.find( "-t", "--test" ) ) {
|
||||
if( cmd.argsCount() == 0 )
|
||||
throw std::domain_error( cmd.name() + " expected at least 1 argument but recieved none" );
|
||||
for( std::size_t i = 0; i < cmd.argsCount(); ++i )
|
||||
config.addTestSpec( cmd[i] );
|
||||
}
|
||||
|
||||
if( Command cmd = parser.find( "-r", "--reporter" ) ) {
|
||||
if( cmd.argsCount() != 1 )
|
||||
cmd.raiseError( "Expected one argument" );
|
||||
config.setReporter( cmd[0] );
|
||||
}
|
||||
|
||||
if( Command cmd = parser.find( "-o", "--out" ) ) {
|
||||
if( cmd.argsCount() == 0 )
|
||||
throw std::domain_error( cmd.name() + " expected filename" );
|
||||
if( cmd[0][0] == '%' )
|
||||
config.useStream( cmd[0].substr( 1 ) );
|
||||
else
|
||||
config.setFilename( cmd[0] );
|
||||
}
|
||||
|
||||
if( Command cmd = parser.find( "-s", "--success" ) ) {
|
||||
if( cmd.argsCount() != 0 )
|
||||
throw std::domain_error( cmd.name() + " does not accept arguments" );
|
||||
config.setIncludeWhichResults( Include::SuccessfulResults );
|
||||
}
|
||||
|
||||
if( Command cmd = parser.find( "-b", "--break" ) ) {
|
||||
if( cmd.argsCount() != 0 )
|
||||
throw std::domain_error( cmd.name() + " does not accept arguments" );
|
||||
config.setShouldDebugBreak( true );
|
||||
}
|
||||
|
||||
if( Command cmd = parser.find( "-n", "--name" ) ) {
|
||||
if( cmd.argsCount() != 1 )
|
||||
throw std::domain_error( cmd.name() + " requires exactly one argument (a name)" );
|
||||
config.setName( cmd[0] );
|
||||
}
|
||||
|
||||
if( Command cmd = parser.find( "-h", "-?", "--help" ) ) {
|
||||
if( cmd.argsCount() != 0 )
|
||||
throw std::domain_error( cmd.name() + " does not accept arguments" );
|
||||
config.setShowHelp( true );
|
||||
}
|
||||
}
|
||||
catch( std::exception& ex ) {
|
||||
config.setError( ex.what() );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue