From 1c223b63ba8cf02be256ddb33c2ac336737adeb6 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Mon, 12 Jun 2017 23:04:24 +0100 Subject: [PATCH] Integrated (all) new version of Clara --- CMakeLists.txt | 2 +- include/catch_session.hpp | 46 +- include/external/clara.h | 1054 --------------------- include/external/clara.hpp | 1189 ++++++++++++++++++++++++ include/internal/catch_clara.h | 5 +- include/internal/catch_commandline.hpp | 294 +++--- projects/SelfTest/TestMain.cpp | 293 +++--- 7 files changed, 1465 insertions(+), 1418 deletions(-) delete mode 100644 include/external/clara.h create mode 100644 include/external/clara.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0526ae0f..be37e4d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,7 +112,7 @@ CheckFileList(TOP_LEVEL_HEADERS ${HEADER_DIR}) # Please keep these ordered alphabetically set(EXTERNAL_HEADERS - ${HEADER_DIR}/external/clara.h + ${HEADER_DIR}/external/clara.hpp ${HEADER_DIR}/external/tbc_text_format.h ) CheckFileList(EXTERNAL_HEADERS ${HEADER_DIR}/external) diff --git a/include/catch_session.hpp b/include/catch_session.hpp index 486d093a..9b1d1e87 100644 --- a/include/catch_session.hpp +++ b/include/catch_session.hpp @@ -96,17 +96,14 @@ namespace Catch { } class Session : NonCopyable { - static bool alreadyInstantiated; - public: - struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; - - Session() - : m_cli( makeCommandLineParser() ) { + Session() { + static bool alreadyInstantiated = false; if( alreadyInstantiated ) CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); alreadyInstantiated = true; + m_cli = makeCommandLineParser( m_configData ); } ~Session() { Catch::cleanUp(); @@ -115,29 +112,27 @@ namespace Catch { void showHelp( std::string const& processName ) { Catch::cout() << "\nCatch v" << libraryVersion() << "\n"; - m_cli.usage( Catch::cout(), processName ); + Catch::cout() << m_cli << std::endl; Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; } - int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { - try { - m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); - m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData ); - if( m_configData.showHelp ) - showHelp( m_configData.processName ); - m_config.reset(); - } - catch( std::exception& ex ) { + int applyCommandLine( int argc, char* argv[] ) { + auto result = m_cli.parse( clara::Args( argc, argv ) ); + if( !result ) { { Colour colourGuard( Colour::Red ); Catch::cerr() << "\nError(s) in input:\n" - << Text( ex.what(), TextAttributes().setIndent(2) ) + << Text( result.errorMessage(), TextAttributes().setIndent(2) ) << "\n\n"; } - m_cli.usage( Catch::cout(), m_configData.processName ); + Catch::cerr() << m_cli << std::endl; return (std::numeric_limits::max)(); } + + if( m_configData.showHelp ) + showHelp( m_configData.processName ); + m_config.reset(); return 0; } @@ -146,7 +141,7 @@ namespace Catch { m_config.reset(); } - int run( int argc, char const* const* const argv ) { + int run( int argc, char* argv[] ) { const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); if ( !exceptions.empty() ) { Catch::cerr() << "Errors occured during startup!" << '\n'; @@ -167,7 +162,7 @@ namespace Catch { } #if defined(WIN32) && defined(UNICODE) - int run( int argc, wchar_t const* const* const argv ) { + int run( int argc, wchar_t* const argv[] ) { char **utf8Argv = new char *[ argc ]; @@ -217,11 +212,11 @@ namespace Catch { } } - Clara::CommandLine const& cli() const { + clara::Parser const& cli() const { return m_cli; } - std::vector const& unusedTokens() const { - return m_unusedTokens; + void cli( clara::Parser const& newParser ) { + m_cli = newParser; } ConfigData& configData() { return m_configData; @@ -232,14 +227,11 @@ namespace Catch { return *m_config; } private: - Clara::CommandLine m_cli; - std::vector m_unusedTokens; + clara::Parser m_cli; ConfigData m_configData; std::shared_ptr m_config; }; - bool Session::alreadyInstantiated = false; - } // end namespace Catch #endif // TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED diff --git a/include/external/clara.h b/include/external/clara.h deleted file mode 100644 index bf758eb5..00000000 --- a/include/external/clara.h +++ /dev/null @@ -1,1054 +0,0 @@ -/* - * 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) - */ - -// Version 0.0.2.4 - -// Only use header guard if we are not using an outer namespace -#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) - -#ifndef STITCH_CLARA_OPEN_NAMESPACE -#define TWOBLUECUBES_CLARA_H_INCLUDED -#define STITCH_CLARA_OPEN_NAMESPACE -#define STITCH_CLARA_CLOSE_NAMESPACE -#else -#define STITCH_CLARA_CLOSE_NAMESPACE } -#endif - - -#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE - -// ----------- #included from tbc_text_format.h ----------- - -/* - * Created by Phil on 18/4/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) - */ -// Only use header guard if we are not using an outer namespace -#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) -#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE -#define TBC_TEXT_FORMAT_H_INCLUDED -#endif - -#include -#include -#include -#include -#include - -// Use optional outer namespace -#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE -namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { -#endif - -namespace Tbc { - -#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH - const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; -#else - const unsigned int consoleWidth = 80; -#endif - - struct TextAttributes { - TextAttributes() - : initialIndent( std::string::npos ), - indent( 0 ), - width( consoleWidth-1 ), - tabChar( '\t' ) - {} - - TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } - TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } - TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } - TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } - - std::size_t initialIndent; // indent of first line, or npos - std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos - std::size_t width; // maximum width of text, including indent. Longer text will wrap - char tabChar; // If this char is seen the indent is changed to current pos - }; - - class Text { - public: - Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) - : attr( _attr ) - { - std::string wrappableChars = " [({.,/|\\-"; - std::size_t indent = _attr.initialIndent != std::string::npos - ? _attr.initialIndent - : _attr.indent; - std::string remainder = _str; - - while( !remainder.empty() ) { - if( lines.size() >= 1000 ) { - lines.push_back( "... message truncated due to excessive size" ); - return; - } - std::size_t tabPos = std::string::npos; - std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); - std::size_t pos = remainder.find_first_of( '\n' ); - if( pos <= width ) { - width = pos; - } - pos = remainder.find_last_of( _attr.tabChar, width ); - if( pos != std::string::npos ) { - tabPos = pos; - if( remainder[width] == '\n' ) - width--; - remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); - } - - if( width == remainder.size() ) { - spliceLine( indent, remainder, width ); - } - else if( remainder[width] == '\n' ) { - spliceLine( indent, remainder, width ); - if( width <= 1 || remainder.size() != 1 ) - remainder = remainder.substr( 1 ); - indent = _attr.indent; - } - else { - pos = remainder.find_last_of( wrappableChars, width ); - if( pos != std::string::npos && pos > 0 ) { - spliceLine( indent, remainder, pos ); - if( remainder[0] == ' ' ) - remainder = remainder.substr( 1 ); - } - else { - spliceLine( indent, remainder, width-1 ); - lines.back() += "-"; - } - if( lines.size() == 1 ) - indent = _attr.indent; - if( tabPos != std::string::npos ) - indent += tabPos; - } - } - } - - void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { - lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); - _remainder = _remainder.substr( _pos ); - } - - typedef std::vector::const_iterator const_iterator; - - const_iterator begin() const { return lines.begin(); } - const_iterator end() const { return lines.end(); } - std::string const& last() const { return lines.back(); } - std::size_t size() const { return lines.size(); } - std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } - std::string toString() const { - std::ostringstream oss; - oss << *this; - return oss.str(); - } - - inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { - for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); - it != itEnd; ++it ) { - if( it != _text.begin() ) - _stream << "\n"; - _stream << *it; - } - return _stream; - } - - private: - std::string str; - TextAttributes attr; - std::vector lines; - }; - -} // end namespace Tbc - -#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE -} // end outer namespace -#endif - -#endif // TBC_TEXT_FORMAT_H_INCLUDED - -// ----------- end of #include from tbc_text_format.h ----------- -// ........... back in clara.h - -#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE - - -// ----------- #included from clara_compilers.h ----------- - -/* - * Created by Phil on 10/02/2016. - * Copyright 2016 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_COMPILERS_H_INCLUDED -#define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED - -// Detect a number of compiler features - mostly C++11/14 conformance - by compiler -// The following features are defined: -// -// CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported? -// CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported? -// CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods -// CLARA_CONFIG_CPP11_OVERRIDE : is override supported? -// CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) - -// CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported? - -// CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported? - -// In general each macro has a _NO_ form -// (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature. -// Many features, at point of detection, define an _INTERNAL_ macro, so they -// can be combined, en-mass, with the _NO_ forms later. - -// All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11 - -#ifdef __clang__ - -#if __has_feature(cxx_nullptr) -#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR -#endif - -#if __has_feature(cxx_noexcept) -#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT -#endif - -#endif // __clang__ - -//////////////////////////////////////////////////////////////////////////////// -// GCC -#ifdef __GNUC__ - -#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) -#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR -#endif - -// - otherwise more recent versions define __cplusplus >= 201103L -// and will get picked up below - -#endif // __GNUC__ - -//////////////////////////////////////////////////////////////////////////////// -// Visual C++ -#ifdef _MSC_VER - -#if (_MSC_VER >= 1600) -#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR -#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR -#endif - -#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) -#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT -#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS -#endif - -#endif // _MSC_VER - - -//////////////////////////////////////////////////////////////////////////////// -// C++ language feature support - -// catch all support for C++11 -#if defined(__cplusplus) && __cplusplus >= 201103L - -#define CLARA_CPP11_OR_GREATER - -#if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) -#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR -#endif - -#ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT -#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT -#endif - -#ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS -#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS -#endif - -#if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) -#define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE -#endif -#if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) -#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR -#endif - - -#endif // __cplusplus >= 201103L - -// Now set the actual defines based on the above + anything the user has configured -#if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11) -#define CLARA_CONFIG_CPP11_NULLPTR -#endif -#if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11) -#define CLARA_CONFIG_CPP11_NOEXCEPT -#endif -#if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11) -#define CLARA_CONFIG_CPP11_GENERATED_METHODS -#endif -#if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11) -#define CLARA_CONFIG_CPP11_OVERRIDE -#endif -#if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11) -#define CLARA_CONFIG_CPP11_UNIQUE_PTR -#endif - - -// noexcept support: -#if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT) -#define CLARA_NOEXCEPT noexcept -# define CLARA_NOEXCEPT_IS(x) noexcept(x) -#else -#define CLARA_NOEXCEPT throw() -# define CLARA_NOEXCEPT_IS(x) -#endif - -// nullptr support -#ifdef CLARA_CONFIG_CPP11_NULLPTR -#define CLARA_NULL nullptr -#else -#define CLARA_NULL NULL -#endif - -// override support -#ifdef CLARA_CONFIG_CPP11_OVERRIDE -#define CLARA_OVERRIDE override -#else -#define CLARA_OVERRIDE -#endif - -// unique_ptr support -#ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR -# define CLARA_AUTO_PTR( T ) std::unique_ptr -#else -# define CLARA_AUTO_PTR( T ) std::auto_ptr -#endif - -#endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED - - -// ----------- end of #include from clara_compilers.h ----------- -// ........... back in clara.h - - -#include -#include -#include - -#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) -#define CLARA_PLATFORM_WINDOWS -#endif - - -// Use optional outer namespace -#ifdef STITCH_CLARA_OPEN_NAMESPACE -STITCH_CLARA_OPEN_NAMESPACE -#endif - -namespace Clara { - - struct UnpositionalTag {}; - - extern UnpositionalTag _; - -#ifdef CLARA_CONFIG_MAIN - UnpositionalTag _; -#endif - - namespace Detail { - -#ifdef CLARA_CONSOLE_WIDTH - const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; -#else - const unsigned int consoleWidth = 80; -#endif - - using namespace Tbc; - - inline bool startsWith( std::string const& str, std::string const& prefix ) { - return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; - } - - 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; - } - char toLowerCh(char c) { - return static_cast( std::tolower( c ) ); - } - inline void convertInto( std::string const& _source, bool& _dest ) { - std::string sourceLC = _source; - std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), toLowerCh ); - if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) - _dest = true; - else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) - _dest = false; - else - throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); - } - - - template - struct IArgFunction { - virtual ~IArgFunction() {} -#ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS - IArgFunction() = default; - IArgFunction( IArgFunction const& ) = default; -#endif - virtual void set( ConfigT& config, std::string const& value ) const = 0; - virtual bool takesArg() const = 0; - virtual IArgFunction* clone() const = 0; - }; - - template - class BoundArgFunction { - public: - BoundArgFunction() : functionObj( CLARA_NULL ) {} - BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} - BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {} - BoundArgFunction& operator = ( BoundArgFunction const& other ) { - IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL; - delete functionObj; - functionObj = newFunctionObj; - return *this; - } - ~BoundArgFunction() { delete functionObj; } - - void set( ConfigT& config, std::string const& value ) const { - functionObj->set( config, value ); - } - bool takesArg() const { return functionObj->takesArg(); } - - bool isSet() const { - return functionObj != CLARA_NULL; - } - private: - IArgFunction* functionObj; - }; - - - template - struct NullBinder : IArgFunction{ - virtual void set( C&, std::string const& ) const {} - virtual bool takesArg() const { return true; } - virtual IArgFunction* clone() const { return new NullBinder( *this ); } - }; - - 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 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 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 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 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 bool takesArg() const { return !IsBool::value; } - virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } - void (*function)( C&, T ); - }; - - } // namespace Detail - - inline std::vector argsToVector( int argc, char const* const* const argv ) { - std::vector args( static_cast( argc ) ); - for( std::size_t i = 0; i < static_cast( argc ); ++i ) - args[i] = argv[i]; - - return args; - } - - class Parser { - enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional }; - Mode mode; - std::size_t from; - bool inQuotes; - public: - - struct Token { - enum Type { Positional, ShortOpt, LongOpt }; - Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} - Type type; - std::string data; - }; - - Parser() : mode( None ), from( 0 ), inQuotes( false ){} - - void parseIntoTokens( std::vector const& args, std::vector& tokens ) { - const std::string doubleDash = "--"; - for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i ) - parseIntoTokens( args[i], tokens); - } - - void parseIntoTokens( std::string const& arg, std::vector& tokens ) { - for( std::size_t i = 0; i < arg.size(); ++i ) { - char c = arg[i]; - if( c == '"' ) - inQuotes = !inQuotes; - mode = handleMode( i, c, arg, tokens ); - } - mode = handleMode( arg.size(), '\0', arg, tokens ); - } - Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { - switch( mode ) { - case None: return handleNone( i, c ); - case MaybeShortOpt: return handleMaybeShortOpt( i, c ); - case ShortOpt: - case LongOpt: - case SlashOpt: return handleOpt( i, c, arg, tokens ); - case Positional: return handlePositional( i, c, arg, tokens ); - default: throw std::logic_error( "Unknown mode" ); - } - } - - Mode handleNone( std::size_t i, char c ) { - if( inQuotes ) { - from = i; - return Positional; - } - switch( c ) { - case '-': return MaybeShortOpt; -#ifdef CLARA_PLATFORM_WINDOWS - case '/': from = i+1; return SlashOpt; -#endif - default: from = i; return Positional; - } - } - Mode handleMaybeShortOpt( std::size_t i, char c ) { - switch( c ) { - case '-': from = i+1; return LongOpt; - default: from = i; return ShortOpt; - } - } - - Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { - if( std::string( ":=\0", 3 ).find( c ) == std::string::npos ) - return mode; - - std::string optName = arg.substr( from, i-from ); - if( mode == ShortOpt ) - for( std::size_t j = 0; j < optName.size(); ++j ) - tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) ); - else if( mode == SlashOpt && optName.size() == 1 ) - tokens.push_back( Token( Token::ShortOpt, optName ) ); - else - tokens.push_back( Token( Token::LongOpt, optName ) ); - return None; - } - Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { - if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos ) - return mode; - - std::string data = arg.substr( from, i-from ); - tokens.push_back( Token( Token::Positional, data ) ); - return None; - } - }; - - template - struct CommonArgProperties { - CommonArgProperties() {} - CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} - - Detail::BoundArgFunction boundField; - std::string description; - std::string detail; - std::string placeholder; // Only value if boundField takes an arg - - bool takesArg() const { - return !placeholder.empty(); - } - void validate() const { - if( !boundField.isSet() ) - throw std::logic_error( "option not bound" ); - } - }; - struct OptionArgProperties { - std::vector shortNames; - std::string longName; - - bool hasShortName( std::string const& shortName ) const { - return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); - } - bool hasLongName( std::string const& _longName ) const { - return _longName == longName; - } - }; - struct PositionalArgProperties { - PositionalArgProperties() : position( -1 ) {} - int position; // -1 means non-positional (floating) - - bool isFixedPositional() const { - return position != -1; - } - }; - - template - class CommandLine { - - struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { - Arg() {} - Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} - - using CommonArgProperties::placeholder; // !TBD - - std::string dbgName() const { - if( !longName.empty() ) - return "--" + longName; - if( !shortNames.empty() ) - return "-" + shortNames[0]; - return "positional args"; - } - 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( !placeholder.empty() ) - oss << " <" << placeholder << ">"; - return oss.str(); - } - }; - - typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr; - - friend void addOptName( Arg& arg, std::string const& optName ) - { - if( optName.empty() ) - return; - if( Detail::startsWith( optName, "--" ) ) { - if( !arg.longName.empty() ) - throw std::logic_error( "Only one long opt may be specified. '" - + arg.longName - + "' already specified, now attempting to add '" - + optName + "'" ); - arg.longName = optName.substr( 2 ); - } - else if( Detail::startsWith( optName, "-" ) ) - arg.shortNames.push_back( optName.substr( 1 ) ); - else - throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); - } - friend void setPositionalArg( Arg& arg, int position ) - { - arg.position = position; - } - - - class ArgBuilder { - public: - ArgBuilder( Arg* arg ) : m_arg( arg ) {} - - // Bind a non-boolean data member (requires placeholder string) - template - void bind( M C::* field, std::string const& placeholder ) { - m_arg->boundField = new Detail::BoundDataMember( field ); - m_arg->placeholder = placeholder; - } - // Bind a boolean data member (no placeholder required) - template - void bind( bool C::* field ) { - m_arg->boundField = new Detail::BoundDataMember( field ); - } - - // Bind a method taking a single, non-boolean argument (requires a placeholder string) - template - void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { - m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); - m_arg->placeholder = placeholder; - } - - // Bind a method taking a single, boolean argument (no placeholder string required) - template - void bind( void (C::* unaryMethod)( bool ) ) { - m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); - } - - // Bind a method that takes no arguments (will be called if opt is present) - template - void bind( void (C::* nullaryMethod)() ) { - m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); - } - - // Bind a free function taking a single argument - the object to operate on (no placeholder string required) - template - void bind( void (* unaryFunction)( C& ) ) { - m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); - } - - // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) - template - void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { - m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); - m_arg->placeholder = placeholder; - } - - ArgBuilder& describe( std::string const& description ) { - m_arg->description = description; - return *this; - } - ArgBuilder& detail( std::string const& detail ) { - m_arg->detail = detail; - return *this; - } - - protected: - Arg* m_arg; - }; - - class OptBuilder : public ArgBuilder { - public: - OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} - OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} - - OptBuilder& operator[]( std::string const& optName ) { - addOptName( *ArgBuilder::m_arg, optName ); - return *this; - } - }; - - public: - - CommandLine() - : m_boundProcessName( new Detail::NullBinder() ), - m_highestSpecifiedArgPosition( 0 ), - m_throwOnUnrecognisedTokens( false ) - {} - CommandLine( CommandLine const& other ) - : m_boundProcessName( other.m_boundProcessName ), - m_options ( other.m_options ), - m_positionalArgs( other.m_positionalArgs ), - m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), - m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) - { - if( other.m_floatingArg.get() ) - m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); - } - - CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { - m_throwOnUnrecognisedTokens = shouldThrow; - return *this; - } - - - OptBuilder operator[]( std::string const& optName ) { - m_options.push_back( Arg() ); - addOptName( m_options.back(), optName ); - OptBuilder builder( &m_options.back() ); - return builder; - } - - ArgBuilder operator[]( int position ) { - m_positionalArgs.insert( std::make_pair( position, Arg() ) ); - if( position > m_highestSpecifiedArgPosition ) - m_highestSpecifiedArgPosition = position; - setPositionalArg( m_positionalArgs[position], position ); - ArgBuilder builder( &m_positionalArgs[position] ); - return builder; - } - - // Invoke this with the _ instance - ArgBuilder operator[]( UnpositionalTag ) { - if( m_floatingArg.get() ) - throw std::logic_error( "Only one unpositional argument can be added" ); - m_floatingArg.reset( new Arg() ); - ArgBuilder builder( m_floatingArg.get() ); - return builder; - } - - template - void bindProcessName( M C::* field ) { - m_boundProcessName = new Detail::BoundDataMember( field ); - } - template - void bindProcessName( void (C::*_unaryMethod)( M ) ) { - m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); - } - - void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) 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 ) { - Detail::Text usage( it->commands(), Detail::TextAttributes() - .setWidth( maxWidth+indent ) - .setIndent( indent ) ); - Detail::Text desc( it->description, Detail::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.placeholder << ">"; - else if( m_floatingArg.get() ) - os << "<" << m_floatingArg->placeholder << ">"; - else - throw std::logic_error( "non consecutive positional arguments with no floating args" ); - } - // !TBD No indication of mandatory args - if( m_floatingArg.get() ) { - if( m_highestSpecifiedArgPosition > 1 ) - os << " "; - os << "[<" << m_floatingArg->placeholder << "> ...]"; - } - } - std::string argSynopsis() const { - std::ostringstream oss; - argSynopsis( oss ); - return oss.str(); - } - - void usage( std::ostream& os, std::string const& procName ) const { - validate(); - 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(); - } - - ConfigT parse( std::vector const& args ) const { - ConfigT config; - parseInto( args, config ); - return config; - } - - std::vector parseInto( std::vector const& args, ConfigT& config ) const { - std::string processName = args.empty() ? std::string() : args[0]; - std::size_t lastSlash = processName.find_last_of( "/\\" ); - if( lastSlash != std::string::npos ) - processName = processName.substr( lastSlash+1 ); - m_boundProcessName.set( config, processName ); - std::vector tokens; - Parser parser; - parser.parseIntoTokens( args, tokens ); - return populate( tokens, config ); - } - - std::vector populate( std::vector const& tokens, ConfigT& config ) const { - validate(); - 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; - std::vector errors; - 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; - - try { - 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 ) - errors.push_back( "Expected argument to option: " + token.data ); - else - arg.boundField.set( config, tokens[++i].data ); - } - else { - arg.boundField.set( config, "true" ); - } - break; - } - } - catch( std::exception& ex ) { - errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); - } - } - if( it == itEnd ) { - if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) - unusedTokens.push_back( token ); - else if( errors.empty() && m_throwOnUnrecognisedTokens ) - errors.push_back( "unrecognised option: " + token.data ); - } - } - if( !errors.empty() ) { - std::ostringstream oss; - for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); - it != itEnd; - ++it ) { - if( it != errors.begin() ) - oss << "\n"; - oss << *it; - } - throw std::runtime_error( oss.str() ); - } - 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_floatingArg.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_floatingArg->boundField.set( config, token.data ); - else - unusedTokens.push_back( token ); - } - return unusedTokens; - } - - void validate() const - { - if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) - throw std::logic_error( "No options or arguments specified" ); - - for( typename std::vector::const_iterator it = m_options.begin(), - itEnd = m_options.end(); - it != itEnd; ++it ) - it->validate(); - } - - private: - Detail::BoundArgFunction m_boundProcessName; - std::vector m_options; - std::map m_positionalArgs; - ArgAutoPtr m_floatingArg; - int m_highestSpecifiedArgPosition; - bool m_throwOnUnrecognisedTokens; - }; - -} // end namespace Clara - - -STITCH_CLARA_CLOSE_NAMESPACE -#undef STITCH_CLARA_OPEN_NAMESPACE -#undef STITCH_CLARA_CLOSE_NAMESPACE - - -#endif // TWOBLUECUBES_CLARA_H_INCLUDED diff --git a/include/external/clara.hpp b/include/external/clara.hpp new file mode 100644 index 00000000..1c0a6884 --- /dev/null +++ b/include/external/clara.hpp @@ -0,0 +1,1189 @@ +// v1.0 +// See https://github.com/philsquared/Clara + +#ifndef CATCH_CLARA_HPP_INCLUDED +#define CATCH_CLARA_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#ifndef CLARA_CONFIG_CONSOLE_WIDTH +#define CLARA_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { +namespace clara { + +// Included from TextFlow.hpp +namespace TextFlow { + + inline auto isWhitespace( char c ) -> bool { + static std::string chars = " \t\n\r"; + return chars.find( c ) != std::string::npos; + } + inline auto isBreakableBefore( char c ) -> bool { + static std::string chars = "[({<|"; + return chars.find( c ) != std::string::npos; + } + inline auto isBreakableAfter( char c ) -> bool { + static std::string chars = "])}>.,:;*+-=&/\\"; + return chars.find( c ) != std::string::npos; + } + + class Columns; + + class Column { + std::vector m_strings; + size_t m_width = CLARA_CONFIG_CONSOLE_WIDTH; + size_t m_indent = 0; + size_t m_initialIndent = std::string::npos; + + public: + class iterator { + friend Column; + + Column const& m_column; + size_t m_stringIndex = 0; + size_t m_pos = 0; + + size_t m_len; + bool m_suffix = false; + + iterator( Column const& column, size_t stringIndex ) + : m_column( column ), + m_stringIndex( stringIndex ) + {} + + auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } + + auto isBoundary( size_t at ) const -> bool { + assert( at > 0 ); + assert( at <= line().size() ); + + return at == line().size() || + ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || + isBreakableBefore( line()[at] ) || + isBreakableAfter( line()[at-1] ); + } + + void calcLength() { + assert( m_stringIndex < m_column.m_strings.size() ); + + m_suffix = false; + auto width = m_column.m_width-indent(); + if( line().size() < m_pos + width ) { + m_len = line().size() - m_pos; + } + else { + size_t len = width; + while (len > 0 && !isBoundary(m_pos + len)) + --len; + while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) + --len; + + if (len > 0) { + m_len = len; + } else { + m_suffix = true; + m_len = width - 1; + } + } + } + + auto indent() const -> size_t { + auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + auto addIndentAndSuffix(std::string const &plain) const -> std::string { + return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); + } + + public: + explicit iterator( Column const& column ) : m_column( column ) { + assert( m_column.m_width > m_column.m_indent ); + assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); + calcLength(); + if( m_len == 0 ) + m_stringIndex++; // Empty string + } + + auto operator *() const -> std::string { + assert( m_stringIndex < m_column.m_strings.size() ); + assert( m_pos < line().size() ); + if( m_pos + m_column.m_width < line().size() ) + return addIndentAndSuffix(line().substr(m_pos, m_len)); + else + return addIndentAndSuffix(line().substr(m_pos)); + } + + auto operator ++() -> iterator& { + m_pos += m_len; + while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) + ++m_pos; + + if( m_pos == line().size() ) { + m_pos = 0; + ++m_stringIndex; + } + if( m_stringIndex < m_column.m_strings.size() ) + calcLength(); + return *this; + } + auto operator ++(int) -> iterator { + iterator prev( *this ); + operator++(); + return prev; + } + + auto operator ==( iterator const& other ) const -> bool { + return + m_pos == other.m_pos && + m_stringIndex == other.m_stringIndex && + &m_column == &other.m_column; + } + auto operator !=( iterator const& other ) const -> bool { + return !operator==( other ); + } + }; + using const_iterator = iterator; + + explicit Column( std::string const& text ) { m_strings.push_back( text ); } + + auto width( size_t newWidth ) -> Column& { + assert( newWidth > 0 ); + m_width = newWidth; + return *this; + } + auto indent( size_t newIndent ) -> Column& { + m_indent = newIndent; + return *this; + } + auto initialIndent( size_t newIndent ) -> Column& { + m_initialIndent = newIndent; + return *this; + } + + auto width() const -> size_t { return m_width; } + auto begin() const -> iterator { return iterator( *this ); } + auto end() const -> iterator { return iterator( *this, m_strings.size() ); } + + inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { + bool first = true; + for( auto line : col ) { + if( first ) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto operator + ( Column const& other ) -> Columns; + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + }; + + class Spacer : public Column { + + public: + Spacer( size_t spaceWidth ) : Column( "" ) { + width( spaceWidth ); + } + }; + + class Columns { + std::vector m_columns; + + public: + + class iterator { + friend Columns; + struct EndTag {}; + + std::vector const& m_columns; + std::vector m_iterators; + size_t m_activeIterators; + + iterator( Columns const& columns, EndTag ) + : m_columns( columns.m_columns ), + m_activeIterators( 0 ) + { + m_iterators.reserve( m_columns.size() ); + + for( auto const& col : m_columns ) + m_iterators.push_back( col.end() ); + } + + public: + iterator( Columns const& columns ) + : m_columns( columns.m_columns ), + m_activeIterators( m_columns.size() ) + { + m_iterators.reserve( m_columns.size() ); + + for( auto const& col : m_columns ) + m_iterators.push_back( col.begin() ); + } + + auto operator ==( iterator const& other ) const -> bool { + return m_iterators == other.m_iterators; + } + auto operator !=( iterator const& other ) const -> bool { + return m_iterators != other.m_iterators; + } + auto operator *() const -> std::string { + std::string row, padding; + + for( size_t i = 0; i < m_columns.size(); ++i ) { + auto width = m_columns[i].width(); + if( m_iterators[i] != m_columns[i].end() ) { + std::string col = *m_iterators[i]; + row += padding + col; + if( col.size() < width ) + padding = std::string( width - col.size(), ' ' ); + else + padding = ""; + } + else { + padding += std::string( width, ' ' ); + } + } + return row; + } + auto operator ++() -> iterator& { + for( size_t i = 0; i < m_columns.size(); ++i ) { + if (m_iterators[i] != m_columns[i].end()) + ++m_iterators[i]; + } + return *this; + } + auto operator ++(int) -> iterator { + iterator prev( *this ); + operator++(); + return prev; + } + }; + using const_iterator = iterator; + + auto begin() const -> iterator { return iterator( *this ); } + auto end() const -> iterator { return iterator( *this, iterator::EndTag() ); } + + auto operator += ( Column const& col ) -> Columns& { + m_columns.push_back( col ); + return *this; + } + auto operator + ( Column const& col ) -> Columns { + Columns combined = *this; + combined += col; + return combined; + } + + inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { + + bool first = true; + for( auto line : cols ) { + if( first ) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + }; + + inline auto Column::operator + ( Column const& other ) -> Columns { + Columns cols; + cols += *this; + cols += other; + return cols; + } + +} // namespace TextFlow + +namespace detail { + + // Traits for extracting arg and return type of lambdas (for single argument lambdas) + template + struct UnaryLambdaTraits : UnaryLambdaTraits {}; + + template + struct UnaryLambdaTraits { + static const bool isValid = false; + }; + + template + struct UnaryLambdaTraits { + static const bool isValid = true; + using ArgType = typename std::remove_const::type>::type;; + using ReturnType = ReturnT; + }; + + class TokenStream; + + // Transport for raw args (copied from main args, or supplied via init list for testing) + class Args { + friend TokenStream; + std::string m_exeName; + std::vector m_args; + + public: + Args(int argc, char *argv[]) { + m_exeName = argv[0]; + for (int i = 1; i < argc; ++i) + m_args.push_back(argv[i]); + } + + Args(std::initializer_list args) + : m_exeName( *args.begin() ), + m_args( args.begin()+1, args.end() ) + {} + + auto exeName() const -> std::string { + return m_exeName; + } + }; + + // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string + // may encode an option + its argument if the : or = form is used + enum class TokenType { + Option, Argument + }; + struct Token { + TokenType type; + std::string token; + }; + + // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled + class TokenStream { + using Iterator = std::vector::const_iterator; + Iterator it; + Iterator itEnd; + std::vector m_tokenBuffer; + + void loadBuffer() { + m_tokenBuffer.resize(0); + + // Skip any empty strings + while (it != itEnd && it->empty()) + ++it; + + if (it != itEnd) { + auto const &next = *it; + if (next[0] == '-' || next[0] == '/') { + auto delimiterPos = next.find_first_of(" :="); + if (delimiterPos != std::string::npos) { + m_tokenBuffer.push_back({TokenType::Option, next.substr(0, delimiterPos)}); + m_tokenBuffer.push_back({TokenType::Argument, next.substr(delimiterPos + 1)}); + } else { + if (next[1] != '-' && next.size() > 2) { + std::string opt = "- "; + for (size_t i = 1; i < next.size(); ++i) { + opt[1] = next[i]; + m_tokenBuffer.push_back({TokenType::Option, opt}); + } + } else { + m_tokenBuffer.push_back({TokenType::Option, next}); + } + } + } else { + m_tokenBuffer.push_back({TokenType::Argument, next}); + } + } + } + + public: + explicit TokenStream(Args const &args) : TokenStream(args.m_args.begin(), args.m_args.end()) {} + + TokenStream(Iterator it, Iterator itEnd) : it(it), itEnd(itEnd) { + loadBuffer(); + } + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } + + auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } + + auto operator*() const -> Token { + assert(!m_tokenBuffer.empty()); + return m_tokenBuffer.front(); + } + + auto operator->() const -> Token const * { + assert(!m_tokenBuffer.empty()); + return &m_tokenBuffer.front(); + } + + auto operator++() -> TokenStream & { + if (m_tokenBuffer.size() >= 2) { + m_tokenBuffer.erase(m_tokenBuffer.begin()); + } else { + if (it != itEnd) + ++it; + loadBuffer(); + } + return *this; + } + }; + + + class ResultBase { + public: + enum Type { + Ok, LogicError, RuntimeError + }; + + protected: + ResultBase(Type type) : m_type(type) {} + + virtual void enforceOk() const = 0; + + Type m_type; + }; + + template + class ResultValueBase : public ResultBase { + public: + auto value() const -> T const & { + enforceOk(); + return m_value; + } + + protected: + ResultValueBase(Type type) : ResultBase(type) {} + + ResultValueBase(ResultValueBase const &other) : ResultBase(other) { + if (m_type == ResultBase::Ok) + new(&m_value) T(other.m_value); + } + + ResultValueBase(Type type, T const &value) + : ResultBase(Ok) { + new(&m_value) T(value); + } + + auto operator=(ResultValueBase const &other) -> ResultValueBase & { + if (m_type == ResultBase::Ok) + m_value.~T(); + ResultBase::operator=(other); + if (m_type == ResultBase::Ok) + new(&m_value) T(other.m_value); + return *this; + } + + ~ResultValueBase() { + if (m_type == Ok) + m_value.~T(); + } + + union { + T m_value; + }; + }; + + template<> + class ResultValueBase : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template + class BasicResult : public ResultValueBase { + public: + template + explicit BasicResult(BasicResult const &other) + : ResultValueBase(other.type()), + m_errorMessage(other.errorMessage()) { + assert(type() != ResultBase::Ok); + } + + static auto ok() -> BasicResult { return {ResultBase::Ok}; } + + template + static auto ok(U const &value) -> BasicResult { return {ResultBase::Ok, value}; } + + static auto logicError(std::string const &message) -> BasicResult { return {ResultBase::LogicError, message}; } + + static auto runtimeError(std::string const &message) -> BasicResult { + return {ResultBase::RuntimeError, message}; + } + + explicit operator bool() const { return m_type == ResultBase::Ok; } + + auto type() const -> ResultBase::Type { return m_type; } + + auto errorMessage() const -> std::string { return m_errorMessage; } + + protected: + virtual void enforceOk() const { + // !TBD: If no exceptions, std::terminate here or something + switch (m_type) { + case ResultBase::LogicError: + throw std::logic_error(m_errorMessage); + case ResultBase::RuntimeError: + throw std::runtime_error(m_errorMessage); + case ResultBase::Ok: + break; + } + } + + std::string m_errorMessage; // Only populated if resultType is an error + + BasicResult(ResultBase::Type type, std::string const &message) + : ResultValueBase(type), + m_errorMessage(message) { + assert(m_type != ResultBase::Ok); + } + + using ResultValueBase::ResultValueBase; + using ResultBase::m_type; + }; + + enum class ParseResultType { + Matched, NoMatch, ShortCircuitAll, ShortCircuitSame + }; + + class ParseState { + public: + + ParseState(ParseResultType type, TokenStream const &remainingTokens) + : m_type(type), + m_remainingTokens(remainingTokens) {} + + auto type() const -> ParseResultType { return m_type; } + + auto remainingTokens() const -> TokenStream { return m_remainingTokens; } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult; + using ParserResult = BasicResult; + using InternalParseResult = BasicResult; + + struct HelpColumns { + std::string left; + std::string right; + }; + + template + inline auto convertInto(std::string const &source, T& target) -> ParserResult { + std::stringstream ss; + ss << source; + ss >> target; + if (ss.fail()) + return ParserResult::runtimeError("Unable to convert '" + source + "' to destination type"); + else + return ParserResult::ok(ParseResultType::Matched); + } + inline auto convertInto(std::string const &source, std::string& target) -> ParserResult { + target = source; + return ParserResult::ok(ParseResultType::Matched); + } + + inline auto convertInto(std::string const &source, bool &target) -> ParserResult { + std::string srcLC = source; + std::transform(srcLC.begin(), srcLC.end(), srcLC.begin(), ::tolower); + if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") + target = true; + else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") + target = false; + else + return ParserResult::runtimeError("Expected a boolean value but did not recognise: '" + source + "'"); + return ParserResult::ok(ParseResultType::Matched); + } + + struct BoundRefBase { + BoundRefBase() = default; + + BoundRefBase(BoundRefBase const &) = delete; + + BoundRefBase(BoundRefBase &&) = delete; + + BoundRefBase &operator=(BoundRefBase const &) = delete; + + BoundRefBase &operator=(BoundRefBase &&) = delete; + + virtual ~BoundRefBase() = default; + + virtual auto isFlag() const -> bool = 0; + + virtual auto isContainer() const -> bool { return false; } + + virtual auto setValue(std::string const &arg) -> ParserResult = 0; + + virtual auto setFlag(bool flag) -> ParserResult = 0; + }; + + struct BoundValueRefBase : BoundRefBase { + auto isFlag() const -> bool override { return false; } + + auto setFlag(bool) -> ParserResult override { + return ParserResult::logicError("Flags can only be set on boolean fields"); + } + }; + + struct BoundFlagRefBase : BoundRefBase { + auto isFlag() const -> bool override { return true; } + + auto setValue(std::string const &arg) -> ParserResult override { + bool flag; + auto result = convertInto(arg, flag); + if (result) + setFlag(flag); + return result; + } + }; + + template + struct BoundRef : BoundValueRefBase { + T &m_ref; + + explicit BoundRef(T &ref) : m_ref(ref) {} + + auto setValue(std::string const &arg) -> ParserResult override { + return convertInto(arg, m_ref); + } + }; + + template + struct BoundRef> : BoundValueRefBase { + std::vector &m_ref; + + explicit BoundRef(std::vector &ref) : m_ref(ref) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue(std::string const &arg) -> ParserResult override { + T temp; + auto result = convertInto(arg, temp); + if (result) + m_ref.push_back(temp); + return result; + } + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool &m_ref; + + explicit BoundFlagRef(bool &ref) : m_ref(ref) {} + + auto setFlag(bool flag) -> ParserResult override { + m_ref = flag; + return ParserResult::ok(ParseResultType::Matched); + } + }; + + template + struct LambdaInvoker { + static_assert(std::is_same::value, "Lambda must return void or clara::ParserResult"); + + template + static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult { + return lambda(arg); + } + }; + + template<> + struct LambdaInvoker { + template + static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult { + lambda(arg); + return ParserResult::ok(ParseResultType::Matched); + } + }; + + template + inline auto invokeLambda(L const &lambda, std::string const &arg) -> ParserResult { + ArgType temp; + auto result = convertInto(arg, temp); + return !result + ? result + : LambdaInvoker::ReturnType>::invoke(lambda, temp); + }; + + + template + struct BoundLambda : BoundValueRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); + explicit BoundLambda(L const &lambda) : m_lambda(lambda) {} + + auto setValue(std::string const &arg) -> ParserResult override { + return invokeLambda::ArgType>(m_lambda, arg); + } + }; + + template + struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); + static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" ); + + explicit BoundFlagLambda(L const &lambda) : m_lambda(lambda) {} + + auto setFlag(bool flag) -> ParserResult override { + return LambdaInvoker::ReturnType>::invoke(m_lambda, flag); + } + }; + + enum class Optionality { + Optional, Required + }; + + struct Parser; + + class ParserBase { + public: + virtual auto validate() const -> Result { return Result::ok(); } + + virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; + + virtual auto cardinality() const -> size_t { return 1; } + + auto parse(Args const &args) const -> InternalParseResult { + return parse( args.exeName(), TokenStream(args)); + } + }; + + template + class ComposableParserImpl : public ParserBase { + public: + template + auto operator+(T const &other) const -> Parser; + }; + + // Common code and state for Args and Opts + template + class ParserRefImpl : public ComposableParserImpl { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr m_ref; + std::string m_hint; + std::string m_description; + + explicit ParserRefImpl(std::shared_ptr const &ref) : m_ref(ref) {} + + public: + template + ParserRefImpl(T &ref, std::string const &hint) : m_ref(std::make_shared>(ref)), m_hint(hint) {} + + template + ParserRefImpl(LambdaT const &ref, std::string const &hint) : m_ref(std::make_shared>(ref)), + m_hint(hint) {} + + auto operator()(std::string const &description) -> DerivedT & { + m_description = description; + return static_cast( *this ); + } + + auto optional() -> DerivedT & { + m_optionality = Optionality::Optional; + return static_cast( *this ); + }; + + auto required() -> DerivedT & { + m_optionality = Optionality::Required; + return static_cast( *this ); + }; + + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override { + if (m_ref->isContainer()) + return 0; + else + return 1; + } + + auto hint() const -> std::string { return m_hint; } + }; + + class ExeName : public ComposableParserImpl { + std::shared_ptr m_name; + std::shared_ptr m_ref; + + template + static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { + return std::make_shared>(lambda); + } + + public: + ExeName() : m_name(std::make_shared("")) {} + + explicit ExeName(std::string &ref) : ExeName() { + m_ref = std::make_shared>( ref ); + } + + template + explicit ExeName( LambdaT const& lambda ) : ExeName() { + m_ref = std::make_shared>( lambda ); + } + + // The exe name is not parsed out of the normal tokens, but is handled specially + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); + } + + auto name() const -> std::string { return *m_name; } + auto set( std::string const& newName ) -> ParserResult { + + auto lastSlash = newName.find_last_of( "\\/" ); + auto filename = (lastSlash == std::string::npos) + ? newName + : newName.substr( lastSlash+1 ); + + *m_name = filename; + if( m_ref ) + return m_ref->setValue( filename ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + class Arg : public ParserRefImpl { + public: + using ParserRefImpl::ParserRefImpl; + + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + auto result = validate(); + if (!result) + return InternalParseResult(result); + + auto remainingTokens = tokens; + auto const &token = *remainingTokens; + if (token.type != TokenType::Argument) + return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens)); + + result = m_ref->setValue(remainingTokens->token); + if (!result) + return InternalParseResult(result); + else + return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens)); + } + }; + + inline auto normaliseOpt(std::string const &optName) -> std::string { + if (optName[0] == '/') + return "-" + optName.substr(1); + else + return optName; + } + + class Opt : public ParserRefImpl { + protected: + std::vector m_optNames; + + public: + using ParserRefImpl::ParserRefImpl; + + template + explicit Opt( LambdaT const &ref ) : ParserRefImpl(std::make_shared>(ref)) {} + + explicit Opt( bool &ref ) : ParserRefImpl(std::make_shared(ref)) {} + + auto operator[](std::string const &optName) -> Opt & { + m_optNames.push_back(optName); + return *this; + } + + auto getHelpColumns() const -> std::vector { + std::ostringstream oss; + bool first = true; + for (auto const &opt : m_optNames) { + if (first) + first = false; + else + oss << ", "; + oss << opt; + } + if (!m_hint.empty()) + oss << " <" << m_hint << ">"; + return {{oss.str(), m_description}}; + } + + auto isMatch(std::string const &optToken) const -> bool { +#ifdef CLARA_PLATFORM_WINDOWS + auto normalisedToken = normaliseOpt( optToken ); +#else + auto const &normalisedToken = optToken; +#endif + for (auto const &name : m_optNames) { + if (normaliseOpt(name) == normalisedToken) + return true; + } + return false; + } + + using ParserBase::parse; + + auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + auto result = validate(); + if (!result) + return InternalParseResult(result); + + auto remainingTokens = tokens; + if (remainingTokens && remainingTokens->type == TokenType::Option) { + auto const &token = *remainingTokens; + if (isMatch(token.token)) { + if (m_ref->isFlag()) { + auto result = m_ref->setFlag(true); + if (!result) + return InternalParseResult(result); + if (result.value() == ParseResultType::ShortCircuitAll) + return InternalParseResult::ok(ParseState(result.value(), remainingTokens)); + } else { + ++remainingTokens; + if (!remainingTokens) + return InternalParseResult::runtimeError("Expected argument following " + token.token); + auto const &argToken = *remainingTokens; + if (argToken.type != TokenType::Argument) + return InternalParseResult::runtimeError("Expected argument following " + token.token); + auto result = m_ref->setValue(argToken.token); + if (!result) + return InternalParseResult(result); + if (result.value() == ParseResultType::ShortCircuitAll) + return InternalParseResult::ok(ParseState(result.value(), remainingTokens)); + } + return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens)); + } + } + return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens)); + } + + auto validate() const -> Result override { + if (m_optNames.empty()) + return Result::logicError("No options supplied to Opt"); + for (auto const &name : m_optNames) { + if (name.empty()) + return Result::logicError("Option name cannot be empty"); + if (name[0] != '-' && name[0] != '/') + return Result::logicError("Option name must begin with '-' or '/'"); + } + return ParserRefImpl::validate(); + } + }; + + struct Help : Opt { + Help( bool &showHelpFlag ) + : Opt([&]( bool flag ) { + showHelpFlag = flag; + return ParserResult::ok(ParseResultType::ShortCircuitAll); + }) + { + static_cast(*this) + ("display usage information") + ["-?"]["-h"]["--help"] + .optional(); + } + }; + + + struct Parser : ParserBase { + + mutable ExeName m_exeName; + std::vector m_options; + std::vector m_args; + + auto operator+=(ExeName const &exeName) -> Parser & { + m_exeName = exeName; + return *this; + } + + auto operator+=(Arg const &arg) -> Parser & { + m_args.push_back(arg); + return *this; + } + + auto operator+=(Opt const &opt) -> Parser & { + m_options.push_back(opt); + return *this; + } + + auto operator+=(Parser const &other) -> Parser & { + m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); + m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); + return *this; + } + + template + auto operator+(T const &other) const -> Parser { + return Parser(*this) += other; + } + + auto getHelpColumns() const -> std::vector { + std::vector cols; + for (auto const &o : m_options) { + auto childCols = o.getHelpColumns(); + cols.insert(cols.end(), childCols.begin(), childCols.end()); + } + return cols; + } + + void writeToStream(std::ostream &os) const { + if (!m_exeName.name().empty()) { + os << "usage:\n" << " " << m_exeName.name() << " "; + bool required = true, first = true; + for (auto const &arg : m_args) { + if (first) + first = false; + else + os << " "; + if (arg.isOptional() && required) { + os << "["; + required = false; + } + os << "<" << arg.hint() << ">"; + if (arg.cardinality() == 0) + os << " ... "; + } + if (!required) + os << "]"; + if (!m_options.empty()) + os << " options"; + os << "\n\nwhere options are:" << std::endl; + } + + auto rows = getHelpColumns(); + size_t optWidth = 0; + for (auto const &cols : rows) + optWidth = std::max(optWidth, cols.left.size() + 2); + + for (auto const &cols : rows) { + auto row = + TextFlow::Column(cols.left).width(optWidth).indent(2) + + TextFlow::Spacer(4) + + TextFlow::Column(cols.right).width(73 - optWidth); + os << row << std::endl; + } + } + + friend auto operator<<(std::ostream &os, Parser const &parser) -> std::ostream & { + parser.writeToStream(os); + return os; + } + + auto validate() const -> Result override { + for (auto const &opt : m_options) { + auto result = opt.validate(); + if (!result) + return result; + } + for (auto const &arg : m_args) { + auto result = arg.validate(); + if (!result) + return result; + } + return Result::ok(); + } + + using ParserBase::parse; + + auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult override { + std::vector allParsers; + allParsers.reserve(m_args.size() + m_options.size()); + std::set requiredParsers; + + for (auto const &opt : m_options) { + allParsers.push_back(&opt); + if (!opt.isOptional()) + requiredParsers.insert(&opt); + } + + size_t optionalArgs = 0; + for (auto const &arg : m_args) { + allParsers.push_back(&arg); + if (!arg.isOptional()) { + if (optionalArgs > 0) + return InternalParseResult::logicError( + "Required arguments must preceed any optional arguments"); + else + ++optionalArgs; + requiredParsers.insert(&arg); + } + } + + m_exeName.set( exeName ); + + auto result = InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); + while (result.value().remainingTokens()) { + int remainingTokenCount = result.value().remainingTokens().count(); + for (auto parser : allParsers) { + result = parser->parse( exeName, result.value().remainingTokens() ); + if (!result || result.value().type() != ParseResultType::NoMatch) { + if (parser->cardinality() == 1) + allParsers.erase(std::remove(allParsers.begin(), allParsers.end(), parser), + allParsers.end()); + requiredParsers.erase(parser); + break; + } + } + if (!result || remainingTokenCount == result.value().remainingTokens().count()) + return result; + } + // !TBD Check missing required options + return result; + } + }; + + template + template + auto ComposableParserImpl::operator+(T const &other) const -> Parser { + return Parser() + static_cast( *this ) + other; + } +} // namespace detail + + +// A Combined parser +using detail::Parser; + +// A parser for options +using detail::Opt; + +// A parser for arguments +using detail::Arg; + +// Wrapper for argc, argv from main() +using detail::Args; + +// Specifies the name of the executable +using detail::ExeName; + +// Convenience wrapper for option parser that specifies the help option +using detail::Help; + +// enum of result types from a parse +using detail::ParseResultType; + +// Result type for parser operation +using detail::ParserResult; + + +} // namespace clara +} // namespace Catch + +#endif // CATCH_CLARA_HPP_INCLUDED diff --git a/include/internal/catch_clara.h b/include/internal/catch_clara.h index bfe2f4b7..9c66a55d 100644 --- a/include/internal/catch_clara.h +++ b/include/internal/catch_clara.h @@ -17,10 +17,7 @@ #define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH -// Declare Clara inside the Catch namespace -#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { -#include "../external/clara.h" -#undef STITCH_CLARA_OPEN_NAMESPACE +#include "../external/clara.hpp" // Restore Clara's value for console width, if present diff --git a/include/internal/catch_commandline.hpp b/include/internal/catch_commandline.hpp index 89c55695..e3e32fa4 100644 --- a/include/internal/catch_commandline.hpp +++ b/include/internal/catch_commandline.hpp @@ -17,190 +17,128 @@ namespace Catch { - inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } - inline void abortAfterX( ConfigData& config, int x ) { - CATCH_ENFORCE( x >=1, "Value after -x or --abortAfter must be greater than zero" ); - config.abortAfter = x; - } - inline void addTestOrTags( ConfigData& config, std::string const& testSpec ) { config.testsOrTags.push_back( testSpec ); } - inline void addSectionToRun( ConfigData& config, std::string const& sectionName ) { config.sectionsToRun.push_back( sectionName ); } - inline void addReporterName( ConfigData& config, std::string const& reporterName ) { config.reporterNames.push_back( reporterName ); } + inline clara::Parser makeCommandLineParser( ConfigData& config ) { - inline void addWarning( ConfigData& config, std::string const& warning ) { - CATCH_ENFORCE( warning == "NoAssertions", "Unrecognised warning: '" << warning << "'" ); - config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); - } - inline void setOrder( ConfigData& config, std::string const& order ) { - if( startsWith( "declared", order ) ) - config.runOrder = RunTests::InDeclarationOrder; - else if( startsWith( "lexical", order ) ) - config.runOrder = RunTests::InLexicographicalOrder; - else if( startsWith( "random", order ) ) - config.runOrder = RunTests::InRandomOrder; - else - CATCH_ENFORCE( false, "Unrecognised ordering: '" << order << '\'' ); - } - inline void setRngSeed( ConfigData& config, std::string const& seed ) { - if( seed == "time" ) { - config.rngSeed = static_cast( std::time(0) ); - } - else { - std::stringstream ss; - ss << seed; - ss >> config.rngSeed; - CATCH_ENFORCE( !ss.fail(), "Argument to --rng-seed should be the word 'time' or a number" ); - } - } - inline void setVerbosity( ConfigData& config, int level ) { - // !TBD: accept strings? - config.verbosity = static_cast( level ); - } - inline void setShowDurations( ConfigData& config, bool _showDurations ) { - config.showDurations = _showDurations - ? ShowDurations::Always - : ShowDurations::Never; - } - inline void setUseColour( ConfigData& config, std::string const& value ) { - std::string mode = toLower( value ); + auto const loadTestNamesFromFile = [&]( std::string const& filename ) { + std::ifstream f( filename.c_str() ); + if( !f.is_open() ) + return clara::ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" ); - if( mode == "yes" ) - config.useColour = UseColour::Yes; - else if( mode == "no" ) - config.useColour = UseColour::No; - else if( mode == "auto" ) - config.useColour = UseColour::Auto; - else - CATCH_ENFORCE( false, "colour mode must be one of: auto, yes or no" ); - } - inline void forceColour( ConfigData& config ) { - config.useColour = UseColour::Yes; - } - inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { - std::ifstream f( _filename.c_str() ); - CATCH_ENFORCE( f.is_open(), "Unable to load input file: '" << _filename << "'" ); + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + config.testsOrTags.push_back( line + ',' ); + } + } + return clara::ParserResult::ok( clara::ParseResultType::Matched ); + }; - std::string line; - while( std::getline( f, line ) ) { - line = trim(line); - if( !line.empty() && !startsWith( line, '#' ) ) { - if( !startsWith( line, '"' ) ) - line = '"' + line + '"'; - addTestOrTags( config, line + ',' ); - } - } - } - inline Clara::CommandLine makeCommandLineParser() { + using namespace clara; + auto cli + = ExeName( config.processName ) + + Help( config.showHelp ) + + Opt( config.listTests ) + ["-l"]["--list-tests"] + ( "list all/matching test cases" ) + + Opt( config.listTags ) + ["-t"]["--list-tags"] + ( "list all/matching tags" ) + + Opt( config.showSuccessfulTests ) + ["-s"]["--success"] + ( "include successful tests in output" ) + + Opt( config.shouldDebugBreak ) + ["-b"]["--break"] + ( "break into debugger on failure" ) + + Opt( config.noThrow ) + ["-e"]["--nothrow"] + ( "skip exception tests" ) + + Opt( config.showInvisibles ) + ["-i"]["--invisibles"] + ( "show invisibles (tabs, newlines)" ) + + Opt( config.outputFilename, "filename" ) + ["-o"]["--out"] + ( "output filename" ) + + Opt( config.reporterNames, "name" ) + ["-r"]["--reporter"] + ( "reporter to use (defaults to console)" ) + + Opt( config.name, "name" ) + ["-n"]["--name"] + ( "suite name" ) + + Opt( [&]( bool ){ config.abortAfter = 1; } ) + ["-a"]["--abort"] + ( "abort at first failure" ) + + Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) + ["-x"]["--abortx"] + ( "abort after x failures" ) + + Opt( [&]( std::string const& warning ) { + if( warning != "NoAssertions" ) + return clara::ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" ); + config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); + return clara::ParserResult::ok( ParseResultType::Matched ); + }, "warning name" ) + ["-w"]["--warn"] + ( "enable warnings" ) + + Opt( [&]( bool ) { config.showDurations = ShowDurations::Always; } ) + ["-d"]["--durations"] + ( "show test durations" ) + + Opt( loadTestNamesFromFile, "filename" ) + ["-f"]["--input-file"] + ( "load test names to run from a file" ) + + Opt( config.filenamesAsTags ) + ["-#"]["--filenames-as-tags"] + ( "adds a tag for the filename" ) + + Opt( config.sectionsToRun, "section name" ) + ["-c"]["--section"] + ( "specify section to run" ) + + Opt( config.listTestNamesOnly ) + ["--list-test-names-only"] + ( "list all/matching test cases names only" ) + + Opt( config.listReporters ) + ["--list-reporters"] + ( "list all reporters" ) + + Opt( [&]( std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" ); + return clara::ParserResult::ok( ParseResultType::Matched ); + }, "decl|lex|rand" ) + ["--order"] + ( "test case order (defaults to decl)" ) + + Opt( [&]( std::string const& seed ) { + if( seed != "time" ) + return clara::detail::convertInto( seed, config.rngSeed ); + config.rngSeed = static_cast( std::time(0) ); + return clara::ParserResult::ok( ParseResultType::Matched ); + }, "'time'|number" ) + ["--rng-seed"] + ( "set a specific seed for random numbers" ) + + Opt( [&]( std::string const& useColour ) { + auto mode = toLower( useColour ); - using namespace Clara; - CommandLine cli; + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + return clara::ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" ); + return clara::ParserResult::ok( ParseResultType::Matched ); + }, "yes|no" ) + ["--use-colour"] + ( "should output be colourised" ) - cli.bindProcessName( &ConfigData::processName ); - - cli["-?"]["-h"]["--help"] - .describe( "display usage information" ) - .bind( &ConfigData::showHelp ); - - cli["-l"]["--list-tests"] - .describe( "list all/matching test cases" ) - .bind( &ConfigData::listTests ); - - cli["-t"]["--list-tags"] - .describe( "list all/matching tags" ) - .bind( &ConfigData::listTags ); - - cli["-s"]["--success"] - .describe( "include successful tests in output" ) - .bind( &ConfigData::showSuccessfulTests ); - - cli["-b"]["--break"] - .describe( "break into debugger on failure" ) - .bind( &ConfigData::shouldDebugBreak ); - - cli["-e"]["--nothrow"] - .describe( "skip exception tests" ) - .bind( &ConfigData::noThrow ); - - cli["-i"]["--invisibles"] - .describe( "show invisibles (tabs, newlines)" ) - .bind( &ConfigData::showInvisibles ); - - cli["-o"]["--out"] - .describe( "output filename" ) - .bind( &ConfigData::outputFilename, "filename" ); - - cli["-r"]["--reporter"] -// .placeholder( "name[:filename]" ) - .describe( "reporter to use (defaults to console)" ) - .bind( &addReporterName, "name" ); - - cli["-n"]["--name"] - .describe( "suite name" ) - .bind( &ConfigData::name, "name" ); - - cli["-a"]["--abort"] - .describe( "abort at first failure" ) - .bind( &abortAfterFirst ); - - cli["-x"]["--abortx"] - .describe( "abort after x failures" ) - .bind( &abortAfterX, "no. failures" ); - - cli["-w"]["--warn"] - .describe( "enable warnings" ) - .bind( &addWarning, "warning name" ); - -// - needs updating if reinstated -// cli.into( &setVerbosity ) -// .describe( "level of verbosity (0=no output)" ) -// .shortOpt( "v") -// .longOpt( "verbosity" ) -// .placeholder( "level" ); - - cli[_] - .describe( "which test or tests to use" ) - .bind( &addTestOrTags, "test name, pattern or tags" ); - - cli["-d"]["--durations"] - .describe( "show test durations" ) - .bind( &setShowDurations, "yes|no" ); - - cli["-f"]["--input-file"] - .describe( "load test names to run from a file" ) - .bind( &loadTestNamesFromFile, "filename" ); - - cli["-#"]["--filenames-as-tags"] - .describe( "adds a tag for the filename" ) - .bind( &ConfigData::filenamesAsTags ); - - cli["-c"]["--section"] - .describe( "specify section to run" ) - .bind( &addSectionToRun, "section name" ); - - // Less common commands which don't have a short form - cli["--list-test-names-only"] - .describe( "list all/matching test cases names only" ) - .bind( &ConfigData::listTestNamesOnly ); - - cli["--list-reporters"] - .describe( "list all reporters" ) - .bind( &ConfigData::listReporters ); - - cli["--order"] - .describe( "test case order (defaults to decl)" ) - .bind( &setOrder, "decl|lex|rand" ); - - cli["--rng-seed"] - .describe( "set a specific seed for random numbers" ) - .bind( &setRngSeed, "'time'|number" ); - - cli["--force-colour"] - .describe( "force colourised output (deprecated)" ) - .bind( &forceColour ); - - cli["--use-colour"] - .describe( "should output be colourised" ) - .bind( &setUseColour, "yes|no" ); + + Arg( config.testsOrTags, "test name|pattern|tags" ) + ( "which test or tests to use" ); return cli; } diff --git a/projects/SelfTest/TestMain.cpp b/projects/SelfTest/TestMain.cpp index 8005a2eb..980d302c 100644 --- a/projects/SelfTest/TestMain.cpp +++ b/projects/SelfTest/TestMain.cpp @@ -25,24 +25,24 @@ CATCH_REGISTER_TAG_ALIAS( "[@tricky]", "[tricky]~[.]" ) #endif -template -void parseIntoConfig( const char * (&argv)[size], Catch::ConfigData& config ) { - Catch::Clara::CommandLine parser = Catch::makeCommandLineParser(); - parser.parseInto( Catch::Clara::argsToVector( size, argv ), config ); -} - -template -std::string parseIntoConfigAndReturnError( const char * (&argv)[size], Catch::ConfigData& config ) { - try { - parseIntoConfig( argv, config ); - FAIL( "expected exception" ); - } - catch( std::exception& ex ) { - return ex.what(); - } - return ""; -} - +//template +//void parseIntoConfig( const char * (&argv)[size], Catch::ConfigData& config ) { +// auto parser = Catch::makeCommandLineParser(); +// parser.parseInto( Catch::Clara::argsToVector( size, argv ), config ); +//} +// +//template +//std::string parseIntoConfigAndReturnError( const char * (&argv)[size], Catch::ConfigData& config ) { +// try { +// parseIntoConfig( argv, config ); +// FAIL( "expected exception" ); +// } +// catch( std::exception& ex ) { +// return ex.what(); +// } +// return ""; +//} +// inline Catch::TestCase fakeTestCase( const char* name, const char* desc = "" ){ return Catch::makeTestCase( nullptr, "", name, desc, CATCH_INTERNAL_LINEINFO ); } TEST_CASE( "Process can be configured on command line", "[config][command-line]" ) { @@ -50,197 +50,182 @@ TEST_CASE( "Process can be configured on command line", "[config][command-line]" using namespace Catch::Matchers; Catch::ConfigData config; + auto cli = Catch::makeCommandLineParser(config); - SECTION( "empty args don't cause a crash" ) { - Catch::Clara::CommandLine parser = Catch::makeCommandLineParser(); - CHECK_NOTHROW( parser.parseInto( std::vector(), config ) ); - - CHECK( config.processName == "" ); + SECTION("empty args don't cause a crash") { + auto result = cli.parse({""}); + CHECK(result); + CHECK(config.processName == ""); } - SECTION( "default - no arguments", "" ) { - const char* argv[] = { "test" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - CHECK( config.processName == "test" ); - CHECK( config.shouldDebugBreak == false ); - CHECK( config.abortAfter == -1 ); - CHECK( config.noThrow == false ); - CHECK( config.reporterNames.empty() ); + SECTION("default - no arguments") { + auto result = cli.parse({"test"}); + CHECK(result); + CHECK(config.processName == "test"); + CHECK(config.shouldDebugBreak == false); + CHECK(config.abortAfter == -1); + CHECK(config.noThrow == false); + CHECK(config.reporterNames.empty()); } - SECTION( "test lists", "" ) { - SECTION( "1 test", "Specify one test case using" ) { - const char* argv[] = { "test", "test1" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("test lists") { + SECTION("1 test", "Specify one test case using") { + auto result = cli.parse({"test", "test1"}); + CHECK(result); - Catch::Config cfg( config ); - REQUIRE( cfg.testSpec().matches( fakeTestCase( "notIncluded" ) ) == false ); - REQUIRE( cfg.testSpec().matches( fakeTestCase( "test1" ) ) ); + Catch::Config cfg(config); + REQUIRE(cfg.testSpec().matches(fakeTestCase("notIncluded")) == false); + REQUIRE(cfg.testSpec().matches(fakeTestCase("test1"))); } - SECTION( "Specify one test case exclusion using exclude:", "" ) { - const char* argv[] = { "test", "exclude:test1" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("Specify one test case exclusion using exclude:") { + auto result = cli.parse({"test", "exclude:test1"}); + CHECK(result); - Catch::Config cfg( config ); - REQUIRE( cfg.testSpec().matches( fakeTestCase( "test1" ) ) == false ); - REQUIRE( cfg.testSpec().matches( fakeTestCase( "alwaysIncluded" ) ) ); + Catch::Config cfg(config); + REQUIRE(cfg.testSpec().matches(fakeTestCase("test1")) == false); + REQUIRE(cfg.testSpec().matches(fakeTestCase("alwaysIncluded"))); } - SECTION( "Specify one test case exclusion using ~", "" ) { - const char* argv[] = { "test", "~test1" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("Specify one test case exclusion using ~") { + auto result = cli.parse({"test", "~test1"}); + CHECK(result); - Catch::Config cfg( config ); - REQUIRE( cfg.testSpec().matches( fakeTestCase( "test1" ) ) == false ); - REQUIRE( cfg.testSpec().matches( fakeTestCase( "alwaysIncluded" ) ) ); + Catch::Config cfg(config); + REQUIRE(cfg.testSpec().matches(fakeTestCase("test1")) == false); + REQUIRE(cfg.testSpec().matches(fakeTestCase("alwaysIncluded"))); } } - SECTION( "reporter", "" ) { - SECTION( "-r/console", "" ) { - const char* argv[] = { "test", "-r", "console" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("reporter") { + SECTION("-r/console") { + CHECK(cli.parse({"test", "-r", "console"})); - REQUIRE( config.reporterNames[0] == "console" ); + REQUIRE(config.reporterNames[0] == "console"); } - SECTION( "-r/xml", "" ) { - const char* argv[] = { "test", "-r", "xml" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("-r/xml") { + CHECK(cli.parse({"test", "-r", "xml"})); - REQUIRE( config.reporterNames[0] == "xml" ); + REQUIRE(config.reporterNames[0] == "xml"); } - SECTION( "-r xml and junit", "" ) { - const char* argv[] = { "test", "-r", "xml", "-r", "junit" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("-r xml and junit") { + CHECK(cli.parse({"test", "-r", "xml", "-r", "junit"})); - REQUIRE( config.reporterNames.size() == 2 ); - REQUIRE( config.reporterNames[0] == "xml" ); - REQUIRE( config.reporterNames[1] == "junit" ); + REQUIRE(config.reporterNames.size() == 2); + REQUIRE(config.reporterNames[0] == "xml"); + REQUIRE(config.reporterNames[1] == "junit"); } - SECTION( "--reporter/junit", "" ) { - const char* argv[] = { "test", "--reporter", "junit" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("--reporter/junit") { + CHECK(cli.parse({"test", "--reporter", "junit"})); - REQUIRE( config.reporterNames[0] == "junit" ); + REQUIRE(config.reporterNames[0] == "junit"); } } - SECTION( "debugger", "" ) { - SECTION( "-b", "" ) { - const char* argv[] = { "test", "-b" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - REQUIRE( config.shouldDebugBreak == true ); + SECTION("debugger") { + SECTION("-b") { + CHECK(cli.parse({"test", "-b"})); + + REQUIRE(config.shouldDebugBreak == true); } - SECTION( "--break", "" ) { - const char* argv[] = { "test", "--break" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("--break") { + CHECK(cli.parse({"test", "--break"})); - REQUIRE( config.shouldDebugBreak ); + REQUIRE(config.shouldDebugBreak); } } - SECTION( "abort", "" ) { - SECTION( "-a aborts after first failure", "" ) { - const char* argv[] = { "test", "-a" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - REQUIRE( config.abortAfter == 1 ); - } - SECTION( "-x 2 aborts after two failures", "" ) { - const char* argv[] = { "test", "-x", "2" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("abort") { + SECTION("-a aborts after first failure") { + CHECK(cli.parse({"test", "-a"})); - REQUIRE( config.abortAfter == 2 ); + REQUIRE(config.abortAfter == 1); } - SECTION( "-x must be greater than zero", "" ) { - const char* argv[] = { "test", "-x", "0" }; - REQUIRE_THAT( parseIntoConfigAndReturnError( argv, config ), Contains( "greater than zero" ) ); + SECTION("-x 2 aborts after two failures") { + CHECK(cli.parse({"test", "-x", "2"})); + + REQUIRE(config.abortAfter == 2); } - SECTION( "-x must be numeric", "" ) { - const char* argv[] = { "test", "-x", "oops" }; - REQUIRE_THAT( parseIntoConfigAndReturnError( argv, config ), Contains( "-x" ) ); + SECTION("-x must be numeric") { + auto result = cli.parse({"test", "-x", "oops"}); + CHECK(!result); + + REQUIRE_THAT(result.errorMessage(), Contains("convert") && Contains("oops")); } } - SECTION( "nothrow", "" ) { - SECTION( "-e", "" ) { - const char* argv[] = { "test", "-e" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("nothrow") { + SECTION("-e") { + CHECK(cli.parse({"test", "-e"})); - REQUIRE( config.noThrow == true ); + REQUIRE(config.noThrow); } - SECTION( "--nothrow", "" ) { - const char* argv[] = { "test", "--nothrow" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("--nothrow") { + CHECK(cli.parse({"test", "--nothrow"})); - REQUIRE( config.noThrow == true ); + REQUIRE(config.noThrow); } } - SECTION( "output filename", "" ) { - SECTION( "-o filename", "" ) { - const char* argv[] = { "test", "-o", "filename.ext" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("output filename") { + SECTION("-o filename") { + CHECK(cli.parse({"test", "-o", "filename.ext"})); - REQUIRE( config.outputFilename == "filename.ext" ); + REQUIRE(config.outputFilename == "filename.ext"); } - SECTION( "--out", "" ) { - const char* argv[] = { "test", "--out", "filename.ext" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("--out") { + CHECK(cli.parse({"test", "--out", "filename.ext"})); - REQUIRE( config.outputFilename == "filename.ext" ); + REQUIRE(config.outputFilename == "filename.ext"); } } - SECTION( "combinations", "" ) { - SECTION( "Single character flags can be combined", "" ) { - const char* argv[] = { "test", "-abe" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION("combinations") { + SECTION("Single character flags can be combined") { + CHECK(cli.parse({"test", "-abe"})); - CHECK( config.abortAfter == 1 ); - CHECK( config.shouldDebugBreak ); - CHECK( config.noThrow == true ); + CHECK(config.abortAfter == 1); + CHECK(config.shouldDebugBreak); + CHECK(config.noThrow == true); } } - SECTION( "use-colour", "") { + + SECTION( "use-colour") { using Catch::UseColour; - SECTION( "without option", "" ) { - const char* argv[] = { "test" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION( "without option" ) { + CHECK(cli.parse({"test"})); REQUIRE( config.useColour == UseColour::Auto ); } - SECTION( "auto", "" ) { - const char* argv[] = { "test", "--use-colour", "auto" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION( "auto" ) { + CHECK(cli.parse({"test", "--use-colour", "auto"})); REQUIRE( config.useColour == UseColour::Auto ); } - SECTION( "yes", "" ) { - const char* argv[] = { "test", "--use-colour", "yes" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION( "yes" ) { + CHECK(cli.parse({"test", "--use-colour", "yes"})); REQUIRE( config.useColour == UseColour::Yes ); } - SECTION( "no", "" ) { - const char* argv[] = { "test", "--use-colour", "no" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION( "no" ) { + CHECK(cli.parse({"test", "--use-colour", "no"})); REQUIRE( config.useColour == UseColour::No ); } - SECTION( "error", "" ) { - const char* argv[] = { "test", "--use-colour", "wrong" }; - REQUIRE_THROWS_WITH( parseIntoConfig( argv, config ), Contains( "colour mode must be one of" ) ); + SECTION( "error" ) { + auto result = cli.parse({"test", "--use-colour", "wrong"}); + CHECK( !result ); + CHECK_THAT( result.errorMessage(), Contains( "colour mode must be one of" ) ); } } } @@ -249,31 +234,31 @@ TEST_CASE( "Process can be configured on command line", "[config][command-line]" TEST_CASE( "Long strings can be wrapped", "[wrap]" ) { using namespace Catch; - SECTION( "plain string", "" ) { + SECTION( "plain string" ) { // guide: 123456789012345678 std::string testString = "one two three four"; - SECTION( "No wrapping", "" ) { + SECTION( "No wrapping" ) { CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); CHECK( Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString ); } - SECTION( "Wrapped once", "" ) { + SECTION( "Wrapped once" ) { CHECK( Text( testString, TextAttributes().setWidth( 17 ) ).toString() == "one two three\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 16 ) ).toString() == "one two three\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 14 ) ).toString() == "one two three\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 13 ) ).toString() == "one two three\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 12 ) ).toString() == "one two\nthree four" ); } - SECTION( "Wrapped twice", "" ) { + SECTION( "Wrapped twice" ) { CHECK( Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" ); } - SECTION( "Wrapped three times", "" ) { + SECTION( "Wrapped three times" ) { CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 5 ) ).toString() == "one\ntwo\nthree\nfour" ); } - SECTION( "Short wrap", "" ) { + SECTION( "Short wrap" ) { CHECK( Text( "abcdef", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef" ); CHECK( Text( "abcdefg", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndefg" ); CHECK( Text( "abcdefgh", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef-\ngh" ); @@ -281,7 +266,7 @@ TEST_CASE( "Long strings can be wrapped", "[wrap]" ) { CHECK( Text( testString, TextAttributes().setWidth( 4 ) ).toString() == "one\ntwo\nthr-\nee\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 3 ) ).toString() == "one\ntwo\nth-\nree\nfo-\nur" ); } - SECTION( "As container", "" ) { + SECTION( "As container" ) { Text text( testString, TextAttributes().setWidth( 6 ) ); REQUIRE( text.size() == 4 ); CHECK( text[0] == "one" ); @@ -289,7 +274,7 @@ TEST_CASE( "Long strings can be wrapped", "[wrap]" ) { CHECK( text[2] == "three" ); CHECK( text[3] == "four" ); } - SECTION( "Indent first line differently", "" ) { + SECTION( "Indent first line differently" ) { Text text( testString, TextAttributes() .setWidth( 10 ) .setIndent( 4 ) @@ -299,43 +284,43 @@ TEST_CASE( "Long strings can be wrapped", "[wrap]" ) { } - SECTION( "With newlines", "" ) { + SECTION( "With newlines" ) { // guide: 1234567890123456789 std::string testString = "one two\nthree four"; - SECTION( "No wrapping" , "" ) { + SECTION( "No wrapping" ) { CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); CHECK( Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString ); CHECK( Text( testString, TextAttributes().setWidth( 10 ) ).toString() == testString ); } - SECTION( "Trailing newline" , "" ) { + SECTION( "Trailing newline" ) { CHECK( Text( "abcdef\n", TextAttributes().setWidth( 10 ) ).toString() == "abcdef" ); CHECK( Text( "abcdef", TextAttributes().setWidth( 6 ) ).toString() == "abcdef" ); CHECK( Text( "abcdef\n", TextAttributes().setWidth( 6 ) ).toString() == "abcdef" ); CHECK( Text( "abcdef\n", TextAttributes().setWidth( 5 ) ).toString() == "abcd-\nef" ); } - SECTION( "Wrapped once", "" ) { + SECTION( "Wrapped once" ) { CHECK( Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" ); } - SECTION( "Wrapped twice", "" ) { + SECTION( "Wrapped twice" ) { CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" ); } } - SECTION( "With wrap-before/ after characters", "" ) { + SECTION( "With wrap-before/ after characters" ) { std::string testString = "one,two(three) "; - SECTION( "No wrapping", "" ) { + SECTION( "No wrapping" ) { CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); CHECK( Text( testString, TextAttributes().setWidth( 24 ) ).toString() == testString ); } - SECTION( "Wrap before", "" ) { + SECTION( "Wrap before" ) { CHECK( Text( testString, TextAttributes().setWidth( 11 ) ).toString() == "one,two\n(three)\n" ); } - SECTION( "Wrap after", "" ) { + SECTION( "Wrap after" ) { CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one,\ntwo\n(thre-\ne)\n" ); CHECK( Text( testString, TextAttributes().setWidth( 5 ) ).toString() == "one,\ntwo\n(thr-\nee)\n" ); CHECK( Text( testString, TextAttributes().setWidth( 4 ) ).toString() == "one,\ntwo\n(th-\nree)\n" ); @@ -416,7 +401,7 @@ private: std::vector colours; }; -TEST_CASE( "replaceInPlace", "" ) { +TEST_CASE( "replaceInPlace" ) { std::string letters = "abcdefcg"; SECTION( "replace single char" ) { CHECK( replaceInPlace( letters, "b", "z" ) ); @@ -469,7 +454,7 @@ TEST_CASE( "Strings can be rendered with colour", "[.colour]" ) { } -TEST_CASE( "Text can be formatted using the Text class", "" ) { +TEST_CASE( "Text can be formatted using the Text class" ) { CHECK( Text( "hi there" ).toString() == "hi there" );