From 10fa0593db7352228793dc0014b713202d288947 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Mon, 27 May 2013 10:52:58 +0100 Subject: [PATCH] Moved Clara into its (her?) own file --- include/internal/clara.h | 519 +++++++++++++++++ projects/SelfTest/CmdLineTests.cpp | 541 +----------------- .../CatchSelfTest.xcodeproj/project.pbxproj | 10 + 3 files changed, 556 insertions(+), 514 deletions(-) create mode 100644 include/internal/clara.h diff --git a/include/internal/clara.h b/include/internal/clara.h new file mode 100644 index 00000000..0fad01dd --- /dev/null +++ b/include/internal/clara.h @@ -0,0 +1,519 @@ +/* + * Created by Phil on 25/05/2013. + * Copyright 2013 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_CLARA_H_INCLUDED +#define TWOBLUECUBES_CLARA_H_INCLUDED + +#include "catch_text.h" // This will get moved out too + +namespace Clara { + namespace Detail { + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + + template struct IsBool { static const bool value = false; }; + template<> struct IsBool { static const bool value = true; }; + + template + void convertInto( std::string const& _source, T& _dest ) { + std::stringstream ss; + ss << _source; + ss >> _dest; + if( ss.fail() ) + throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); + } + inline void convertInto( std::string const& _source, std::string& _dest ) { + _dest = _source; + } + inline void convertInto( std::string const& _source, bool& _dest ) { + std::string sourceLC = _source; + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); + if( sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) + _dest = true; + else if( sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) + _dest = false; + else + throw std::runtime_error( "Expected a boolean value but did recognise: '" + _source + "'" ); + } + inline void convertInto( bool _source, bool& _dest ) { + _dest = _source; + } + template + inline void convertInto( bool, T& ) { + throw std::runtime_error( "Invalid conversion" ); + } + + template + struct IArgFunction { + virtual ~IArgFunction() {} + virtual void set( ConfigT& config, std::string const& value ) const = 0; + virtual void setFlag( ConfigT& config ) const = 0; + virtual bool takesArg() const = 0; + virtual IArgFunction* clone() const = 0; + }; + + template + class BoundArgFunction { + public: + BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj->clone() ) {} + BoundArgFunction& operator = ( BoundArgFunction const& other ) { + IArgFunction newFunctionObj = other.clone(); + delete functionObj; + functionObj = newFunctionObj; + return *this; + } + ~BoundArgFunction() { delete functionObj; } + + void set( ConfigT& config, std::string const& value ) const { + functionObj->set( config, value ); + } + void setFlag( ConfigT& config ) const { + functionObj->setFlag( config ); + } + bool takesArg() const { return functionObj->takesArg(); } + private: + IArgFunction* functionObj; + }; + + + template + struct BoundDataMember : IArgFunction{ + BoundDataMember( M C::* _member ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + convertInto( stringValue, p.*member ); + } + virtual void setFlag( C& p ) const { + convertInto( true, p.*member ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } + M C::* member; + }; + template + struct BoundUnaryMethod : IArgFunction{ + BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + (p.*member)( value ); + } + virtual void setFlag( C& p ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + (p.*member)( value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } + void (C::*member)( M ); + }; + template + struct BoundNullaryMethod : IArgFunction{ + BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + (p.*member)(); + } + virtual void setFlag( C& p ) const { + (p.*member)(); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } + void (C::*member)(); + }; + + template + struct BoundUnaryFunction : IArgFunction{ + BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + function( obj ); + } + virtual void setFlag( C& p ) const { + function( p ); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } + void (*function)( C& ); + }; + + template + struct BoundBinaryFunction : IArgFunction{ + BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + function( obj, value ); + } + virtual void setFlag( C& obj ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + function( obj, value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } + void (*function)( C&, T ); + }; + + template + BoundArgFunction makeBoundField( M C::* _member ) { + return BoundArgFunction( new BoundDataMember( _member ) ); + } + template + BoundArgFunction makeBoundField( void (C::*_member)( M ) ) { + return BoundArgFunction( new BoundUnaryMethod( _member ) ); + } + template + BoundArgFunction makeBoundField( void (C::*_member)() ) { + return BoundArgFunction( new BoundNullaryMethod( _member ) ); + } + template + BoundArgFunction makeBoundField( void (*_function)( C& ) ) { + return BoundArgFunction( new BoundUnaryFunction( _function ) ); + } + template + BoundArgFunction makeBoundField( void (*_function)( C&, T ) ) { + return BoundArgFunction( new BoundBinaryFunction( _function ) ); + } + } // namespace Detail + + struct Parser { + Parser() : separators( " \t=:" ) {} + + struct Token { + enum Type { Positional, ShortOpt, LongOpt }; + Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} + Type type; + std::string data; + }; + + void parseIntoTokens( int argc, char const* argv[], std::vector& tokens ) const { + for( int i = 1; i < argc; ++i ) + parseIntoTokens( argv[i] , tokens); + } + void parseIntoTokens( std::string arg, std::vector& tokens ) const { + while( !arg.empty() ) { + Parser::Token token( Parser::Token::Positional, arg ); + arg = ""; + if( token.data[0] == '-' ) { + if( token.data.size() > 1 && token.data[1] == '-' ) { + token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); + } + else { + token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); + if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { + arg = "-" + token.data.substr( 1 ); + token.data = token.data.substr( 0, 1 ); + } + } + } + if( token.type != Parser::Token::Positional ) { + std::size_t pos = token.data.find_first_of( separators ); + if( pos != std::string::npos ) { + arg = token.data.substr( pos+1 ); + token.data = token.data.substr( 0, pos ); + } + } + tokens.push_back( token ); + } + } + std::string separators; + }; + + template + class CommandLine { + + struct Arg { + Arg( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ), position( -1 ) {} + + bool hasShortName( std::string const& shortName ) const { + for( std::vector::const_iterator + it = shortNames.begin(), itEnd = shortNames.end(); + it != itEnd; + ++it ) + if( *it == shortName ) + return true; + return false; + } + bool hasLongName( std::string const& _longName ) const { + return _longName == longName; + } + bool takesArg() const { + return !argName.empty(); + } + bool isFixedPositional() const { + return position != -1; + } + bool isAnyPositional() const { + return position == -1 && shortNames.empty() && longName.empty(); + } + std::string dbgName() const { + if( !longName.empty() ) + return "--" + longName; + if( !shortNames.empty() ) + return "-" + shortNames[0]; + return "positional args"; + } + void validate() const { + if( boundField.takesArg() && !takesArg() ) + throw std::logic_error( dbgName() + " must specify an arg name" ); + } + std::string commands() const { + std::ostringstream oss; + bool first = true; + std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); + for(; it != itEnd; ++it ) { + if( first ) + first = false; + else + oss << ", "; + oss << "-" << *it; + } + if( !longName.empty() ) { + if( !first ) + oss << ", "; + oss << "--" << longName; + } + if( !argName.empty() ) + oss << " <" << argName << ">"; + return oss.str(); + } + + Detail::BoundArgFunction boundField; + std::vector shortNames; + std::string longName; + std::string description; + std::string argName; + int position; + }; + + class ArgBinder { + public: + template + ArgBinder( CommandLine* cl, F f ) + : m_cl( cl ), + m_arg( Detail::makeBoundField( f ) ) + {} + ArgBinder( ArgBinder& other ) + : m_cl( other.m_cl ), + m_arg( other.m_arg ) + { + other.m_cl = NULL; + } + ~ArgBinder() { + if( m_cl ) { + m_arg.validate(); + if( m_arg.isFixedPositional() ) { + m_cl->m_positionalArgs.insert( std::make_pair( m_arg.position, m_arg ) ); + if( m_arg.position > m_cl->m_highestSpecifiedArgPosition ) + m_cl->m_highestSpecifiedArgPosition = m_arg.position; + } + else if( m_arg.isAnyPositional() ) { + if( m_cl->m_arg.get() ) + throw std::logic_error( "Only one unpositional argument can be added" ); + m_cl->m_arg = std::auto_ptr( new Arg( m_arg ) ); + } + else + m_cl->m_options.push_back( m_arg ); + } + } + ArgBinder& shortOpt( std::string const& name ) { + m_arg.shortNames.push_back( name ); + return *this; + } + ArgBinder& longOpt( std::string const& name ) { + m_arg.longName = name; + return *this; + } + ArgBinder& describe( std::string const& description ) { + m_arg.description = description; + return *this; + } + ArgBinder& argName( std::string const& argName ) { + m_arg.argName = argName; + return *this; + } + ArgBinder& position( int position ) { + m_arg.position = position; + return *this; + } + private: + CommandLine* m_cl; + Arg m_arg; + }; + + public: + + CommandLine() : m_highestSpecifiedArgPosition( 0 ) {} + + template + ArgBinder bind( F f ) { + ArgBinder binder( this, f ); + return binder; + } + + void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = CATCH_CONFIG_CONSOLE_WIDTH ) const { + typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; + std::size_t maxWidth = 0; + for( it = itBegin; it != itEnd; ++it ) + maxWidth = (std::max)( maxWidth, it->commands().size() ); + + for( it = itBegin; it != itEnd; ++it ) { + Catch::Text usage( it->commands(), Catch::TextAttributes() + .setWidth( maxWidth+indent ) + .setIndent( indent ) ); + // !TBD handle longer usage strings + Catch::Text desc( it->description, Catch::TextAttributes() + .setWidth( width - maxWidth -3 ) ); + + for( std::size_t i = 0; i < std::max( usage.size(), desc.size() ); ++i ) { + std::string usageCol = i < usage.size() ? usage[i] : ""; + os << usageCol; + + if( i < desc.size() && !desc[i].empty() ) + os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) + << desc[i]; + os << "\n"; + } + } + } + std::string optUsage() const { + std::ostringstream oss; + optUsage( oss ); + return oss.str(); + } + + void argSynopsis( std::ostream& os ) const { + for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { + if( i > 1 ) + os << " "; + typename std::map::const_iterator it = m_positionalArgs.find( i ); + if( it != m_positionalArgs.end() ) + os << "<" << it->second.argName << ">"; + else if( m_arg.get() ) + os << "<" << m_arg->argName << ">"; + else + throw std::logic_error( "non consecutive positional arguments with no floating args" ); + } + // !TBD No indication of mandatory args + if( m_arg.get() ) { + if( m_highestSpecifiedArgPosition > 1 ) + os << " "; + os << "[<" << m_arg->argName << "> ...]"; + } + } + std::string argSynopsis() const { + std::ostringstream oss; + argSynopsis( oss ); + return oss.str(); + } + + + void usage( std::ostream& os, std::string const& procName ) const { + os << "usage:\n " << procName << " "; + argSynopsis( os ); + if( !m_options.empty() ) { + os << " [options]\n\nwhere options are: \n"; + optUsage( os, 2 ); + } + os << "\n"; + } + std::string usage( std::string const& procName ) const { + std::ostringstream oss; + usage( oss, procName ); + return oss.str(); + } + + std::vector parseInto( int argc, char const* argv[], ConfigT& config ) const { + std::vector tokens; + Parser parser; + parser.parseIntoTokens( argc, argv, tokens ); + return populate( tokens, config ); + } + + std::vector populate( std::vector const& tokens, ConfigT& config ) const { + if( m_options.empty() && m_positionalArgs.empty() ) + throw std::logic_error( "No options or arguments specified" ); + + std::vector unusedTokens = populateOptions( tokens, config ); + unusedTokens = populateFixedArgs( unusedTokens, config ); + unusedTokens = populateFloatingArgs( unusedTokens, config ); + return unusedTokens; + } + + std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); + for(; it != itEnd; ++it ) { + Arg const& arg = *it; + + if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || + ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { + if( arg.takesArg() ) { + if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) + throw std::domain_error( "Expected argument to option " + token.data ); + arg.boundField.set( config, tokens[++i].data ); + } + else { + arg.boundField.setFlag( config ); + } + break; + } + } + if( it == itEnd ) + unusedTokens.push_back( token ); + } + return unusedTokens; + } + std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + int position = 1; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::map::const_iterator it = m_positionalArgs.find( position ); + if( it != m_positionalArgs.end() ) + it->second.boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + if( token.type == Parser::Token::Positional ) + position++; + } + return unusedTokens; + } + std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { + if( !m_arg.get() ) + return tokens; + std::vector unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + if( token.type == Parser::Token::Positional ) + m_arg->boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + } + return unusedTokens; + } + + private: + std::vector m_options; + std::map m_positionalArgs; + std::auto_ptr m_arg; + int m_highestSpecifiedArgPosition; + }; + +} // end namespace Clara + + +#endif // TWOBLUECUBES_CLARA_H_INCLUDED diff --git a/projects/SelfTest/CmdLineTests.cpp b/projects/SelfTest/CmdLineTests.cpp index 137f29fc..9175dcde 100644 --- a/projects/SelfTest/CmdLineTests.cpp +++ b/projects/SelfTest/CmdLineTests.cpp @@ -9,513 +9,10 @@ #pragma clang diagnostic ignored "-Wpadded" #endif +#include "internal/clara.h" + #include "catch.hpp" -#include "catch_text.h" -namespace Clara { - namespace Detail { - template struct RemoveConstRef{ typedef T type; }; - template struct RemoveConstRef{ typedef T type; }; - template struct RemoveConstRef{ typedef T type; }; - template struct RemoveConstRef{ typedef T type; }; - - template struct IsBool { static const bool value = false; }; - template<> struct IsBool { static const bool value = true; }; - - template - void convertInto( std::string const& _source, T& _dest ) { - std::stringstream ss; - ss << _source; - ss >> _dest; - if( ss.fail() ) - throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); - } - inline void convertInto( std::string const& _source, std::string& _dest ) { - _dest = _source; - } - inline void convertInto( std::string const& _source, bool& _dest ) { - std::string sourceLC = _source; - std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); - if( sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) - _dest = true; - else if( sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) - _dest = false; - else - throw std::runtime_error( "Expected a boolean value but did recognise: '" + _source + "'" ); - } - inline void convertInto( bool _source, bool& _dest ) { - _dest = _source; - } - template - inline void convertInto( bool, T& ) { - throw std::runtime_error( "Invalid conversion" ); - } - - template - struct IArgFunction { - virtual ~IArgFunction() {} - virtual void set( ConfigT& config, std::string const& value ) const = 0; - virtual void setFlag( ConfigT& config ) const = 0; - virtual bool takesArg() const = 0; - virtual IArgFunction* clone() const = 0; - }; - - template - class BoundArgFunction { - public: - BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} - BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj->clone() ) {} - BoundArgFunction& operator = ( BoundArgFunction const& other ) { - IArgFunction newFunctionObj = other.clone(); - delete functionObj; - functionObj = newFunctionObj; - return *this; - } - ~BoundArgFunction() { delete functionObj; } - - void set( ConfigT& config, std::string const& value ) const { - functionObj->set( config, value ); - } - void setFlag( ConfigT& config ) const { - functionObj->setFlag( config ); - } - bool takesArg() const { return functionObj->takesArg(); } - private: - IArgFunction* functionObj; - }; - - - template - struct BoundDataMember : IArgFunction{ - BoundDataMember( M C::* _member ) : member( _member ) {} - virtual void set( C& p, std::string const& stringValue ) const { - convertInto( stringValue, p.*member ); - } - virtual void setFlag( C& p ) const { - convertInto( true, p.*member ); - } - virtual bool takesArg() const { return !IsBool::value; } - virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } - M C::* member; - }; - template - struct BoundUnaryMethod : IArgFunction{ - BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} - virtual void set( C& p, std::string const& stringValue ) const { - typename RemoveConstRef::type value; - convertInto( stringValue, value ); - (p.*member)( value ); - } - virtual void setFlag( C& p ) const { - typename RemoveConstRef::type value; - convertInto( true, value ); - (p.*member)( value ); - } - virtual bool takesArg() const { return !IsBool::value; } - virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } - void (C::*member)( M ); - }; - template - struct BoundNullaryMethod : IArgFunction{ - BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} - virtual void set( C& p, std::string const& stringValue ) const { - bool value; - convertInto( stringValue, value ); - if( value ) - (p.*member)(); - } - virtual void setFlag( C& p ) const { - (p.*member)(); - } - virtual bool takesArg() const { return false; } - virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } - void (C::*member)(); - }; - - template - struct BoundUnaryFunction : IArgFunction{ - BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} - virtual void set( C& obj, std::string const& stringValue ) const { - bool value; - convertInto( stringValue, value ); - if( value ) - function( obj ); - } - virtual void setFlag( C& p ) const { - function( p ); - } - virtual bool takesArg() const { return false; } - virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } - void (*function)( C& ); - }; - - template - struct BoundBinaryFunction : IArgFunction{ - BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} - virtual void set( C& obj, std::string const& stringValue ) const { - typename RemoveConstRef::type value; - convertInto( stringValue, value ); - function( obj, value ); - } - virtual void setFlag( C& obj ) const { - typename RemoveConstRef::type value; - convertInto( true, value ); - function( obj, value ); - } - virtual bool takesArg() const { return !IsBool::value; } - virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } - void (*function)( C&, T ); - }; - - template - BoundArgFunction makeBoundField( M C::* _member ) { - return BoundArgFunction( new BoundDataMember( _member ) ); - } - template - BoundArgFunction makeBoundField( void (C::*_member)( M ) ) { - return BoundArgFunction( new BoundUnaryMethod( _member ) ); - } - template - BoundArgFunction makeBoundField( void (C::*_member)() ) { - return BoundArgFunction( new BoundNullaryMethod( _member ) ); - } - template - BoundArgFunction makeBoundField( void (*_function)( C& ) ) { - return BoundArgFunction( new BoundUnaryFunction( _function ) ); - } - template - BoundArgFunction makeBoundField( void (*_function)( C&, T ) ) { - return BoundArgFunction( new BoundBinaryFunction( _function ) ); - } - } // namespace Detail - - struct Parser { - Parser() : separators( " \t=:" ) {} - - struct Token { - enum Type { Positional, ShortOpt, LongOpt }; - Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} - Type type; - std::string data; - }; - - void parseIntoTokens( int argc, char const* argv[], std::vector& tokens ) const { - for( int i = 1; i < argc; ++i ) - parseIntoTokens( argv[i] , tokens); - } - void parseIntoTokens( std::string arg, std::vector& tokens ) const { - while( !arg.empty() ) { - Parser::Token token( Parser::Token::Positional, arg ); - arg = ""; - if( token.data[0] == '-' ) { - if( token.data.size() > 1 && token.data[1] == '-' ) { - token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); - } - else { - token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); - if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { - arg = "-" + token.data.substr( 1 ); - token.data = token.data.substr( 0, 1 ); - } - } - } - if( token.type != Parser::Token::Positional ) { - std::size_t pos = token.data.find_first_of( separators ); - if( pos != std::string::npos ) { - arg = token.data.substr( pos+1 ); - token.data = token.data.substr( 0, pos ); - } - } - tokens.push_back( token ); - } - } - std::string separators; - }; - - template - class CommandLine { - - struct Arg { - Arg( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ), position( -1 ) {} - - bool hasShortName( std::string const& shortName ) const { - for( std::vector::const_iterator - it = shortNames.begin(), itEnd = shortNames.end(); - it != itEnd; - ++it ) - if( *it == shortName ) - return true; - return false; - } - bool hasLongName( std::string const& _longName ) const { - return _longName == longName; - } - bool takesArg() const { - return !argName.empty(); - } - bool isFixedPositional() const { - return position != -1; - } - bool isAnyPositional() const { - return position == -1 && shortNames.empty() && longName.empty(); - } - std::string dbgName() const { - if( !longName.empty() ) - return "--" + longName; - if( !shortNames.empty() ) - return "-" + shortNames[0]; - return "positional args"; - } - void validate() const { - if( boundField.takesArg() && !takesArg() ) - throw std::logic_error( dbgName() + " must specify an arg name" ); - } - std::string commands() const { - std::ostringstream oss; - bool first = true; - std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); - for(; it != itEnd; ++it ) { - if( first ) - first = false; - else - oss << ", "; - oss << "-" << *it; - } - if( !longName.empty() ) { - if( !first ) - oss << ", "; - oss << "--" << longName; - } - if( !argName.empty() ) - oss << " <" << argName << ">"; - return oss.str(); - } - - Detail::BoundArgFunction boundField; - std::vector shortNames; - std::string longName; - std::string description; - std::string argName; - int position; - }; - - class ArgBinder { - public: - template - ArgBinder( CommandLine* cl, F f ) - : m_cl( cl ), - m_arg( Detail::makeBoundField( f ) ) - {} - ArgBinder( ArgBinder& other ) - : m_cl( other.m_cl ), - m_arg( other.m_arg ) - { - other.m_cl = NULL; - } - ~ArgBinder() { - if( m_cl ) { - m_arg.validate(); - if( m_arg.isFixedPositional() ) { - m_cl->m_positionalArgs.insert( std::make_pair( m_arg.position, m_arg ) ); - if( m_arg.position > m_cl->m_highestSpecifiedArgPosition ) - m_cl->m_highestSpecifiedArgPosition = m_arg.position; - } - else if( m_arg.isAnyPositional() ) { - if( m_cl->m_arg.get() ) - throw std::logic_error( "Only one unpositional argument can be added" ); - m_cl->m_arg = std::auto_ptr( new Arg( m_arg ) ); - } - else - m_cl->m_options.push_back( m_arg ); - } - } - ArgBinder& shortOpt( std::string const& name ) { - m_arg.shortNames.push_back( name ); - return *this; - } - ArgBinder& longOpt( std::string const& name ) { - m_arg.longName = name; - return *this; - } - ArgBinder& describe( std::string const& description ) { - m_arg.description = description; - return *this; - } - ArgBinder& argName( std::string const& argName ) { - m_arg.argName = argName; - return *this; - } - ArgBinder& position( int position ) { - m_arg.position = position; - return *this; - } - private: - CommandLine* m_cl; - Arg m_arg; - }; - - public: - - CommandLine() : m_highestSpecifiedArgPosition( 0 ) {} - - template - ArgBinder bind( F f ) { - ArgBinder binder( this, f ); - return binder; - } - - void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = CATCH_CONFIG_CONSOLE_WIDTH ) const { - typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; - std::size_t maxWidth = 0; - for( it = itBegin; it != itEnd; ++it ) - maxWidth = (std::max)( maxWidth, it->commands().size() ); - - for( it = itBegin; it != itEnd; ++it ) { - Catch::Text usage( it->commands(), Catch::TextAttributes() - .setWidth( maxWidth+indent ) - .setIndent( indent ) ); - // !TBD handle longer usage strings - Catch::Text desc( it->description, Catch::TextAttributes() - .setWidth( width - maxWidth -3 ) ); - - for( std::size_t i = 0; i < std::max( usage.size(), desc.size() ); ++i ) { - std::string usageCol = i < usage.size() ? usage[i] : ""; - os << usageCol; - - if( i < desc.size() && !desc[i].empty() ) - os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) - << desc[i]; - os << "\n"; - } - } - } - std::string optUsage() const { - std::ostringstream oss; - optUsage( oss ); - return oss.str(); - } - - void argSynopsis( std::ostream& os ) const { - for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { - if( i > 1 ) - os << " "; - typename std::map::const_iterator it = m_positionalArgs.find( i ); - if( it != m_positionalArgs.end() ) - os << "<" << it->second.argName << ">"; - else if( m_arg.get() ) - os << "<" << m_arg->argName << ">"; - else - throw std::logic_error( "non consecutive positional arguments with no floating args" ); - } - // !TBD No indication of mandatory args - if( m_arg.get() ) { - if( m_highestSpecifiedArgPosition > 1 ) - os << " "; - os << "[<" << m_arg->argName << "> ...]"; - } - } - std::string argSynopsis() const { - std::ostringstream oss; - argSynopsis( oss ); - return oss.str(); - } - - - void usage( std::ostream& os, std::string const& procName ) const { - os << "usage:\n " << procName << " "; - argSynopsis( os ); - if( !m_options.empty() ) { - os << " [options]\n\nwhere options are: \n"; - optUsage( os, 2 ); - } - os << "\n"; - } - std::string usage( std::string const& procName ) const { - std::ostringstream oss; - usage( oss, procName ); - return oss.str(); - } - - std::vector parseInto( int argc, char const* argv[], ConfigT& config ) const { - std::vector tokens; - Parser parser; - parser.parseIntoTokens( argc, argv, tokens ); - return populate( tokens, config ); - } - - std::vector populate( std::vector const& tokens, ConfigT& config ) const { - if( m_options.empty() && m_positionalArgs.empty() ) - throw std::logic_error( "No options or arguments specified" ); - - std::vector unusedTokens = populateOptions( tokens, config ); - unusedTokens = populateFixedArgs( unusedTokens, config ); - unusedTokens = populateFloatingArgs( unusedTokens, config ); - return unusedTokens; - } - - std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { - std::vector unusedTokens; - for( std::size_t i = 0; i < tokens.size(); ++i ) { - Parser::Token const& token = tokens[i]; - typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); - for(; it != itEnd; ++it ) { - Arg const& arg = *it; - - if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || - ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { - if( arg.takesArg() ) { - if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) - throw std::domain_error( "Expected argument to option " + token.data ); - arg.boundField.set( config, tokens[++i].data ); - } - else { - arg.boundField.setFlag( config ); - } - break; - } - } - if( it == itEnd ) - unusedTokens.push_back( token ); - } - return unusedTokens; - } - std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { - std::vector unusedTokens; - int position = 1; - for( std::size_t i = 0; i < tokens.size(); ++i ) { - Parser::Token const& token = tokens[i]; - typename std::map::const_iterator it = m_positionalArgs.find( position ); - if( it != m_positionalArgs.end() ) - it->second.boundField.set( config, token.data ); - else - unusedTokens.push_back( token ); - if( token.type == Parser::Token::Positional ) - position++; - } - return unusedTokens; - } - std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { - if( !m_arg.get() ) - return tokens; - std::vector unusedTokens; - for( std::size_t i = 0; i < tokens.size(); ++i ) { - Parser::Token const& token = tokens[i]; - if( token.type == Parser::Token::Positional ) - m_arg->boundField.set( config, token.data ); - else - unusedTokens.push_back( token ); - } - return unusedTokens; - } - - private: - std::vector m_options; - std::map m_positionalArgs; - std::auto_ptr m_arg; - int m_highestSpecifiedArgPosition; - }; - -} // end namespace Clara // Helper to deduce size from array literals and pass on to parser template @@ -684,6 +181,10 @@ TEST_CASE( "cmdline" ) { } struct Config { + + struct Verbosity { enum Level { NoOutput = 0, Quiet, Normal }; }; + struct Warning { enum Types { Nothing = 0x00, NoAssertions = 0x01 }; }; + Config() : listTests( false ), listTags( false ), @@ -692,7 +193,7 @@ struct Config { noThrow( false ), showHelp( false ), abortAfter( 0 ), - verbosity( Normal ) + verbosity( Verbosity::Normal ) {} bool listTests; @@ -705,21 +206,33 @@ struct Config { bool showHelp; int abortAfter; - enum Verbosity { NoOutput = 0, Quiet, Normal }; - Verbosity verbosity; + + Verbosity::Level verbosity; + Warning::Types warnings; + std::string reporterName; std::string fileName; std::string suiteName; std::string reporter; - std::vector warnings; + std::vector testsOrTags; }; inline void abortAfterFirst( Config& config ) { config.abortAfter = 1; } inline void abortAfterX( Config& config, int x ) { config.abortAfter = x; } -inline void addWarning( Config& config, std::string const& _warning ) { config.warnings.push_back( _warning ); } -inline void addTestOrTags( Config& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } -inline void setVerbosity( Config& config, int level ) { config.verbosity = (Config::Verbosity)level; } +inline void addTestOrTags( Config& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + +inline void addWarning( Config& config, std::string const& _warning ) { + if( _warning == "NoAssertions" ) + config.warnings = (Config::Warning::Types)( config.warnings | Config::Warning::NoAssertions ); + else + throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); + +} +inline void setVerbosity( Config& config, int level ) { + // !TBD: accept strings? + config.verbosity = (Config::Verbosity::Level)level; +} SCENARIO( "New Catch commandline interface", "[cli]" ) { @@ -857,13 +370,13 @@ SCENARIO( "New Catch commandline interface", "[cli]" ) { } } WHEN( "And enum opt is set by numeric value" ) { - CHECK( config.verbosity == Config::Normal ); + CHECK( config.verbosity == Config::Verbosity::Normal ); const char* argv[] = { "test", "-v 0" }; parseInto( cli, argv, config ); THEN( "The member is set to the enum value" ) - REQUIRE( config.verbosity == Config::NoOutput ); + REQUIRE( config.verbosity == Config::Verbosity::NoOutput ); } } } diff --git a/projects/XCode4/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj b/projects/XCode4/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj index c178f5ec..3e99d94c 100644 --- a/projects/XCode4/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj +++ b/projects/XCode4/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj @@ -68,6 +68,7 @@ 26847E5C16BBACB60043B9C1 /* catch_message.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = catch_message.hpp; sourceTree = ""; }; 26847E5D16BBADB40043B9C1 /* catch_message.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = catch_message.cpp; path = ../../../SelfTest/SurrogateCpps/catch_message.cpp; sourceTree = ""; }; 2694A1FB16A0000E004816E3 /* catch_text.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = catch_text.cpp; sourceTree = ""; }; + 26C5F3EC17514B970056FB3C /* clara.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = clara.h; path = ../../../../include/internal/clara.h; sourceTree = ""; }; 26DACF2F17206D3400A21326 /* catch_text.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_text.h; sourceTree = ""; }; 4A084F1C15DACEEA0027E631 /* catch_test_case_info.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = catch_test_case_info.hpp; sourceTree = ""; }; 4A084F1D15DAD15F0027E631 /* catch_test_spec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_test_spec.h; sourceTree = ""; }; @@ -186,6 +187,14 @@ name = "Introspective Tests"; sourceTree = ""; }; + 26C5F3EB17514B670056FB3C /* External */ = { + isa = PBXGroup; + children = ( + 26C5F3EC17514B970056FB3C /* clara.h */, + ); + name = External; + sourceTree = ""; + }; 4A6D0C15149B3D3B00DB3EAA = { isa = PBXGroup; children = ( @@ -236,6 +245,7 @@ 4A6D0C41149B3DE900DB3EAA /* Catch */ = { isa = PBXGroup; children = ( + 26C5F3EB17514B670056FB3C /* External */, 4A7DB2CD1652FE4B00FA6523 /* catch_version.h */, 4AA7B8B4165428BA003155F6 /* catch_version.hpp */, 4A8E4DCF160A34E200194CBD /* SurrogateCpps */,