From eb775aa7af1a0734d4588bdaa688aa1cd1605a14 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Sat, 25 Aug 2012 21:26:05 +0100 Subject: [PATCH] Refactored command line parsers into polymorphic classes --- include/catch_runner.hpp | 6 +- include/internal/catch_commandline.hpp | 388 ++++++++++++++++++------- projects/SelfTest/TestMain.cpp | 34 ++- 3 files changed, 325 insertions(+), 103 deletions(-) diff --git a/include/catch_runner.hpp b/include/catch_runner.hpp index 5bda2c9d..0b997129 100644 --- a/include/catch_runner.hpp +++ b/include/catch_runner.hpp @@ -173,7 +173,9 @@ namespace Catch { try { CommandParser parser( argc, argv ); - + + AllOptions options; + if( Command cmd = parser.find( "-h", "-?", "--help" ) ) { if( cmd.argsCount() != 0 ) cmd.raiseError( "Does not accept arguments" ); @@ -183,7 +185,7 @@ namespace Catch { return 0; } - parseIntoConfig( parser, config.data() ); + options.parseIntoConfig( parser, config.data() ); } catch( std::exception& ex ) { std::cerr << ex.what() << "\n\nUsage: ...\n\n"; diff --git a/include/internal/catch_commandline.hpp b/include/internal/catch_commandline.hpp index 89a4b356..5879176f 100644 --- a/include/internal/catch_commandline.hpp +++ b/include/internal/catch_commandline.hpp @@ -72,9 +72,12 @@ namespace Catch { return find( shortArg ) + find( longArg ); } Command find( const std::string& arg ) const { - for( std::size_t i = 1; i < m_argc; ++i ) - if( m_argv[i] == arg ) - return getArgs( m_argv[i], i+1 ); + if( arg.empty() ) + return getArgs( "", 1 ); + else + for( std::size_t i = 1; i < m_argc; ++i ) + if( m_argv[i] == arg ) + return getArgs( m_argv[i], i+1 ); return Command(); } Command getDefaultArgs() const { @@ -92,109 +95,298 @@ namespace Catch { std::size_t m_argc; char const * const * m_argv; }; - - inline void parseIntoConfig( const CommandParser& parser, ConfigData& config ) { + + class OptionParser : public IShared { + public: + virtual ~OptionParser() {} + + void parseIntoConfig( CommandParser parser, ConfigData& config ) { + Command cmd; + for( std::vector::const_iterator it = m_optionNames.begin(); + it != m_optionNames.end(); + ++it ) + cmd += parser.find( *it ); + + if( cmd ) + parseIntoConfig( cmd, config ); + } + + virtual void parseIntoConfig( const Command& cmd, ConfigData& config ) = 0; + virtual std::string argsSynopsis() const = 0; + virtual std::string optionSummary() const = 0; - if( Command cmd = parser.find( "-l", "--list" ) ) { - if( cmd.argsCount() > 2 ) - cmd.raiseError( "Expected upto 2 arguments" ); + protected: + std::vector m_optionNames; + }; - config.listSpec = List::TestNames; - if( cmd.argsCount() >= 1 ) { - if( cmd[0] == "all" ) - config.listSpec = List::All; - else if( cmd[0] == "tests" ) - config.listSpec = List::Tests; - else if( cmd[0] == "reporters" ) - config.listSpec = List::Reports; - else - cmd.raiseError( "Expected [tests] or [reporters]" ); - } - if( cmd.argsCount() >= 2 ) { - if( cmd[1] == "xml" ) - config.listSpec = static_cast( config.listSpec | List::AsXml ); - else if( cmd[1] == "text" ) - config.listSpec = static_cast( config.listSpec | List::AsText ); - else - cmd.raiseError( "Expected [xml] or [text]" ); - } - } - - if( Command cmd = parser.find( "-t", "--test" ) + parser.getDefaultArgs() ) { - if( cmd.argsCount() == 0 ) - cmd.raiseError( "Expected at least one argument" ); - std::string groupName; - for( std::size_t i = 0; i < cmd.argsCount(); ++i ) { - if( i != 0 ) - groupName += " "; - groupName += cmd[i]; - } - TestCaseFilters filters( groupName ); - for( std::size_t i = 0; i < cmd.argsCount(); ++i ) { - if( startsWith( cmd[i], "exclude:" ) ) - filters.addFilter( TestCaseFilter( cmd[i].substr( 8 ), IfFilterMatches::ExcludeTests ) ); - else if( startsWith( cmd[i], "~" ) ) - filters.addFilter( TestCaseFilter( cmd[i].substr( 1 ), IfFilterMatches::ExcludeTests ) ); - else - filters.addFilter( TestCaseFilter( cmd[i] ) ); - } - config.filters.push_back( filters ); - } + namespace Options { - if( Command cmd = parser.find( "-r", "--reporter" ) ) { - if( cmd.argsCount() != 1 ) - cmd.raiseError( "Expected one argument" ); - config.reporter = cmd[0]; - } + class TestCaseOptionParser : public OptionParser { + public: + TestCaseOptionParser() { + m_optionNames.push_back( "-t" ); + m_optionNames.push_back( "--test" ); + m_optionNames.push_back( "" ); // default option + } + virtual std::string argsSynopsis() const { + return " [...]"; + } + virtual std::string optionSummary() const { + return "Specify which test case or cases to run"; + } + + virtual void parseIntoConfig( const Command& cmd, ConfigData& config ) { + if( cmd.argsCount() == 0 ) + cmd.raiseError( "Expected at least one argument" ); + std::string groupName; + for( std::size_t i = 0; i < cmd.argsCount(); ++i ) { + if( i != 0 ) + groupName += " "; + groupName += cmd[i]; + } + TestCaseFilters filters( groupName ); + for( std::size_t i = 0; i < cmd.argsCount(); ++i ) { + if( startsWith( cmd[i], "exclude:" ) ) + filters.addFilter( TestCaseFilter( cmd[i].substr( 8 ), IfFilterMatches::ExcludeTests ) ); + else if( startsWith( cmd[i], "~" ) ) + filters.addFilter( TestCaseFilter( cmd[i].substr( 1 ), IfFilterMatches::ExcludeTests ) ); + else + filters.addFilter( TestCaseFilter( cmd[i] ) ); + } + config.filters.push_back( filters ); + } + }; + + class ListOptionParser : public OptionParser { + public: + ListOptionParser() { + m_optionNames.push_back( "-l" ); + m_optionNames.push_back( "--list" ); + } + virtual std::string argsSynopsis() const { + return "!TBD"; + } + virtual std::string optionSummary() const { + return "!TBD"; + } + + virtual void parseIntoConfig( const Command& cmd, ConfigData& config ) { + if( cmd.argsCount() > 2 ) + cmd.raiseError( "Expected upto 2 arguments" ); + + config.listSpec = List::TestNames; + if( cmd.argsCount() >= 1 ) { + if( cmd[0] == "all" ) + config.listSpec = List::All; + else if( cmd[0] == "tests" ) + config.listSpec = List::Tests; + else if( cmd[0] == "reporters" ) + config.listSpec = List::Reports; + else + cmd.raiseError( "Expected [tests] or [reporters]" ); + } + if( cmd.argsCount() >= 2 ) { + if( cmd[1] == "xml" ) + config.listSpec = static_cast( config.listSpec | List::AsXml ); + else if( cmd[1] == "text" ) + config.listSpec = static_cast( config.listSpec | List::AsText ); + else + cmd.raiseError( "Expected [xml] or [text]" ); + } + } + }; - if( Command cmd = parser.find( "-o", "--out" ) ) { - if( cmd.argsCount() == 0 ) - cmd.raiseError( "Expected filename" ); - if( cmd[0][0] == '%' ) - config.stream = cmd[0].substr( 1 ); - else - config.outputFilename = cmd[0]; - } - - if( Command cmd = parser.find( "-s", "--success" ) ) { - if( cmd.argsCount() != 0 ) - cmd.raiseError( "Does not accept arguments" ); - config.includeWhichResults = Include::SuccessfulResults; - } - - if( Command cmd = parser.find( "-b", "--break" ) ) { - if( cmd.argsCount() != 0 ) - cmd.raiseError( "Does not accept arguments" ); - config.shouldDebugBreak = true; - } - - if( Command cmd = parser.find( "-n", "--name" ) ) { - if( cmd.argsCount() != 1 ) - cmd.raiseError( "Expected a name" ); - config.name = cmd[0]; - } - - if( Command cmd = parser.find( "-a", "--abort" ) ) { - if( cmd.argsCount() > 1 ) - cmd.raiseError( "Only accepts 0-1 arguments" ); - int threshold = 1; - if( cmd.argsCount() == 1 ) { - std::stringstream ss; - ss << cmd[0]; - ss >> threshold; - if( ss.fail() || threshold <= 0 ) - cmd.raiseError( "threshold must be a number greater than zero" ); + class ReporterOptionParser : public OptionParser { + public: + ReporterOptionParser() { + m_optionNames.push_back( "-r" ); + m_optionNames.push_back( "--reporter" ); + } + virtual std::string argsSynopsis() const { + return "!TBD"; + } + virtual std::string optionSummary() const { + return "!TBD"; } - config.cutoff = threshold; - } - if( Command cmd = parser.find( "-nt", "--nothrow" ) ) { - if( cmd.argsCount() != 0 ) - cmd.raiseError( "Does not accept arguments" ); - config.allowThrows = false; - } + virtual void parseIntoConfig( const Command& cmd, ConfigData& config ) { + if( cmd.argsCount() != 1 ) + cmd.raiseError( "Expected one argument" ); + config.reporter = cmd[0]; + } + }; + class OutputOptionParser : public OptionParser { + public: + OutputOptionParser() { + m_optionNames.push_back( "-o" ); + m_optionNames.push_back( "--out" ); + } + virtual std::string argsSynopsis() const { + return "!TBD"; + } + virtual std::string optionSummary() const { + return "!TBD"; + } + + virtual void parseIntoConfig( const Command& cmd, ConfigData& config ) { + if( cmd.argsCount() == 0 ) + cmd.raiseError( "Expected filename" ); + if( cmd[0][0] == '%' ) + config.stream = cmd[0].substr( 1 ); + else + config.outputFilename = cmd[0]; + } + }; + + class SuccesssOptionParser : public OptionParser { + public: + SuccesssOptionParser() { + m_optionNames.push_back( "-s" ); + m_optionNames.push_back( "--success" ); + } + virtual std::string argsSynopsis() const { + return "!TBD"; + } + virtual std::string optionSummary() const { + return "!TBD"; + } + + virtual void parseIntoConfig( const Command& cmd, ConfigData& config ) { + if( cmd.argsCount() != 0 ) + cmd.raiseError( "Does not accept arguments" ); + config.includeWhichResults = Include::SuccessfulResults; + } + }; + + class DebugBreakOptionParser : public OptionParser { + public: + DebugBreakOptionParser() { + m_optionNames.push_back( "-b" ); + m_optionNames.push_back( "--break" ); + } + virtual std::string argsSynopsis() const { + return "!TBD"; + } + virtual std::string optionSummary() const { + return "!TBD"; + } + + virtual void parseIntoConfig( const Command& cmd, ConfigData& config ) { + if( cmd.argsCount() != 0 ) + cmd.raiseError( "Does not accept arguments" ); + config.shouldDebugBreak = true; + } + }; + + class NameOptionParser : public OptionParser { + public: + NameOptionParser() { + m_optionNames.push_back( "-n" ); + m_optionNames.push_back( "--name" ); + } + virtual std::string argsSynopsis() const { + return "!TBD"; + } + virtual std::string optionSummary() const { + return "!TBD"; + } + + virtual void parseIntoConfig( const Command& cmd, ConfigData& config ) { + if( cmd.argsCount() != 1 ) + cmd.raiseError( "Expected a name" ); + config.name = cmd[0]; + } + }; + + class AbortOptionParser : public OptionParser { + public: + AbortOptionParser() { + m_optionNames.push_back( "-a" ); + m_optionNames.push_back( "--abort" ); + } + virtual std::string argsSynopsis() const { + return "!TBD"; + } + virtual std::string optionSummary() const { + return "!TBD"; + } + + virtual void parseIntoConfig( const Command& cmd, ConfigData& config ) { + if( cmd.argsCount() > 1 ) + cmd.raiseError( "Only accepts 0-1 arguments" ); + int threshold = 1; + if( cmd.argsCount() == 1 ) { + std::stringstream ss; + ss << cmd[0]; + ss >> threshold; + if( ss.fail() || threshold <= 0 ) + cmd.raiseError( "threshold must be a number greater than zero" ); + } + config.cutoff = threshold; + } + }; + + class NoThrowOptionParser : public OptionParser { + public: + NoThrowOptionParser() { + m_optionNames.push_back( "-nt" ); + m_optionNames.push_back( "--nothrow" ); + } + virtual std::string argsSynopsis() const { + return "!TBD"; + } + virtual std::string optionSummary() const { + return "!TBD"; + } + + virtual void parseIntoConfig( const Command& cmd, ConfigData& config ) { + if( cmd.argsCount() != 0 ) + cmd.raiseError( "Does not accept arguments" ); + config.allowThrows = false; + } + }; } + + class AllOptions + { + public: + typedef std::vector > Parsers; + typedef Parsers::const_iterator const_iterator; + typedef Parsers::const_iterator iterator; + + AllOptions() { + add(); + add(); + add(); + add(); + add(); + add(); + add(); + add(); + add(); + } + + void parseIntoConfig( const CommandParser& parser, ConfigData& config ) { + for( const_iterator it = m_parsers.begin(); it != m_parsers.end(); ++it ) + (*it)->parseIntoConfig( parser, config ); + } + + const_iterator begin() const { + return m_parsers.begin(); + } + const_iterator end() const { + return m_parsers.end(); + } + private: + + template + void add() { + m_parsers.push_back( new SharedImpl() ); + } + Parsers m_parsers; + + }; } // end namespace Catch diff --git a/projects/SelfTest/TestMain.cpp b/projects/SelfTest/TestMain.cpp index ad1f4842..d7031081 100644 --- a/projects/SelfTest/TestMain.cpp +++ b/projects/SelfTest/TestMain.cpp @@ -13,7 +13,7 @@ TEST_CASE( "selftest/main", "Runs all Catch self tests and checks their results" ) { using namespace Catch; - + /////////////////////////////////////////////////////////////////////////// SECTION( "selftest/expected result", "Tests do what they claim" ) { @@ -70,13 +70,14 @@ TEST_CASE( "meta/Misc/Sections", "looped tests" ) { template void parseIntoConfig( const char * (&argv)[size], Catch::ConfigData& config ) { - Catch::parseIntoConfig( Catch::CommandParser( size, argv ), config ); + static Catch::AllOptions options; + options.parseIntoConfig( Catch::CommandParser( size, argv ), config ); } template std::string parseIntoConfigAndReturnError( const char * (&argv)[size], Catch::ConfigData& config ) { try { - Catch::parseIntoConfig( Catch::CommandParser( size, argv ), config ); + parseIntoConfig( argv, config ); FAIL( "expected exception" ); } catch( std::exception& ex ) { @@ -306,3 +307,30 @@ TEST_CASE( "selftest/filter/wildcard at both ends", "Individual filters with wil CHECK( matchBadgers.shouldInclude( makeTestCase( "badgers are big" ) ) ); CHECK( matchBadgers.shouldInclude( makeTestCase( "hedgehogs" ) ) == false ); } + + +template +int getArgc( const char * (&)[size] ) { + return size; +} + +TEST_CASE( "selftest/option parsers", "" ) +{ + Catch::ConfigData config; + + Catch::SharedImpl tcOpt; + Catch::OptionParser& opt = tcOpt; + + const char* argv[] = { "test", "-t", "test1" }; + + Catch::CommandParser parser( getArgc( argv ), argv ); + + CHECK_NOTHROW( opt.parseIntoConfig( parser, config ) ); + + REQUIRE( config.filters.size() == 1 ); + REQUIRE( config.filters[0].shouldInclude( makeTestCase( "notIncluded" ) ) == false ); + REQUIRE( config.filters[0].shouldInclude( makeTestCase( "test1" ) ) ); + + + +}