From 7059c6e1c32b8db1d5f1c6466bd9a1dd5bc7f04a Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Fri, 19 Apr 2013 19:08:32 +0100 Subject: [PATCH] Text class mostly working - tabs not yet working --- include/catch_runner.hpp | 4 +- include/internal/catch_line_wrap.hpp | 2 +- include/internal/catch_text.h | 120 ++++++++++++++++ include/reporters/catch_reporter_console.hpp | 15 +- projects/SelfTest/TestMain.cpp | 129 +++++++++--------- .../CatchSelfTest.xcodeproj/project.pbxproj | 6 +- 6 files changed, 197 insertions(+), 79 deletions(-) create mode 100644 include/internal/catch_text.h diff --git a/include/catch_runner.hpp b/include/catch_runner.hpp index 30ed6be1..81f0ad5f 100644 --- a/include/catch_runner.hpp +++ b/include/catch_runner.hpp @@ -14,6 +14,7 @@ #include "internal/catch_test_spec.h" #include "internal/catch_version.h" #include "internal/catch_line_wrap.h" +#include "internal/catch_text.h" #include #include @@ -161,7 +162,8 @@ namespace Catch { displayedSpecificOption = true; std::cout << "\n" << opt.optionNames() << " " << opt.argsSynopsis() << "\n\n" << opt.optionSummary() << "\n\n" - << LineWrapper().setIndent( 2 ).wrap( opt.optionDescription() ) << "\n" << std::endl; +// << LineWrapper().setIndent( 2 ).wrap( opt.optionDescription() ) << "\n" << std::endl; + << Text( opt.optionDescription(), TextAttributes().setIndent( 2 ) ) << "\n" << std::endl; } } diff --git a/include/internal/catch_line_wrap.hpp b/include/internal/catch_line_wrap.hpp index 05e9143b..aa278f5d 100644 --- a/include/internal/catch_line_wrap.hpp +++ b/include/internal/catch_line_wrap.hpp @@ -48,7 +48,7 @@ namespace Catch { return wrappableChars.find( c ) != std::string::npos; } void LineWrapper::wrapInternal( std::string const& _str ) { - assert( ++recursionCount < 100 ); + assert( ++recursionCount < 1000 ); std::size_t width = right - getCurrentIndent(); std::size_t wrapPoint = width-tab; diff --git a/include/internal/catch_text.h b/include/internal/catch_text.h new file mode 100644 index 00000000..31c4e56d --- /dev/null +++ b/include/internal/catch_text.h @@ -0,0 +1,120 @@ +/* + * 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) + */ +#ifndef TWOBLUECUBES_CATCH_TEXT_H_INCLUDED +#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED + +#include +#include + +namespace Catch { + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( CATCH_CONFIG_CONSOLE_WIDTH-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; + std::size_t tabPos = std::string::npos; + while( !remainder.empty() ) { + std::size_t width = _attr.width - indent; + std::size_t wrapPos = width; + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + wrapPos = pos; + addLine( indent, remainder.substr( 0, pos ) ); + remainder = remainder.substr( pos+1 ); + if( remainder.empty() ) + addLine (indent, "" ); // Trailing newlines result in extra line + } + else { + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + if( remainder.size() <= width ) { + addLine( indent, remainder ); + remainder = std::string(); + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos == std::string::npos ) { + addLine( indent, remainder.substr( 0, width-1 ) + "-" ); + remainder = remainder.substr( width-1 ); + } + else { + addLine( indent, remainder.substr( 0, pos ) ); + if( remainder[pos] == ' ' ) + pos++; + remainder = remainder.substr( pos ); + } + } + } + indent = tabPos == std::string::npos + ? _attr.indent + : indent + tabPos; + }; + } + void addLine( std::size_t indent, std::string const& _line ) { + lines.push_back( std::string( indent, ' ' ) + _line ); + } + 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]; } + + 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; + } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_TEXT_H_INCLUDED diff --git a/include/reporters/catch_reporter_console.hpp b/include/reporters/catch_reporter_console.hpp index 741f00b5..8326cc2f 100644 --- a/include/reporters/catch_reporter_console.hpp +++ b/include/reporters/catch_reporter_console.hpp @@ -264,10 +264,7 @@ namespace Catch { } } void printTestCaseAndSectionHeader() { - printOpenHeader( unusedTestCaseInfo->name, - currentSectionInfo - ? currentSectionInfo->lineInfo - : unusedTestCaseInfo->lineInfo ); + printOpenHeader( unusedTestCaseInfo->name ); if( currentSectionInfo ) { Colour colourGuard( Colour::Headers ); std::vector sections; @@ -280,7 +277,7 @@ namespace Catch { if( !sections.empty() ) { typedef std::vector::const_reverse_iterator It; for( It it = sections.rbegin(), itEnd = sections.rend(); it != itEnd; ++it ) - printUserString( (*it)->name, 2 ); + printHeaderString( (*it)->name, 2 ); } } @@ -300,17 +297,17 @@ namespace Catch { printOpenHeader( _name ); stream << getDots() << "\n"; } - void printOpenHeader( std::string const& _name, SourceLineInfo const& _lineInfo = SourceLineInfo() ) { + void printOpenHeader( std::string const& _name ) { stream << getDashes() << "\n"; { Colour colourGuard( Colour::Headers ); - printUserString( _name ); + printHeaderString( _name ); } } - + // if string has a : in first line will set indent to follow it on // subsequent lines - void printUserString( std::string const& _string, std::size_t indent = 0 ) { + void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { std::size_t i = _string.find( ": " ); if( i != std::string::npos ) i+=2; diff --git a/projects/SelfTest/TestMain.cpp b/projects/SelfTest/TestMain.cpp index e1a44be0..7e47725e 100644 --- a/projects/SelfTest/TestMain.cpp +++ b/projects/SelfTest/TestMain.cpp @@ -11,6 +11,7 @@ #include "catch_self_test.hpp" #include "internal/catch_line_wrap.h" +#include "internal/catch_text.h" TEST_CASE( "selftest/main", "Runs all Catch self tests and checks their results" ) { using namespace Catch; @@ -427,52 +428,53 @@ TEST_CASE( "selftest/tags", "" ) { TEST_CASE( "Long strings can be wrapped", "[wrap]" ) { + using namespace Catch; SECTION( "plain string", "" ) { // guide: 123456789012345678 std::string testString = "one two three four"; SECTION( "No wrapping", "" ) { - CHECK( Catch::LineWrapper().setRight( 80 ).wrap( testString ).toString() == testString ); - CHECK( Catch::LineWrapper().setRight( 18 ).wrap( testString ).toString() == testString ); + CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); + CHECK( Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString ); } SECTION( "Wrapped once", "" ) { - CHECK( Catch::LineWrapper().setRight( 17 ).wrap( testString ).toString() == "one two three\nfour" ); - CHECK( Catch::LineWrapper().setRight( 16 ).wrap( testString ).toString() == "one two three\nfour" ); - CHECK( Catch::LineWrapper().setRight( 15 ).wrap( testString ).toString() == "one two three\nfour" ); - CHECK( Catch::LineWrapper().setRight( 14 ).wrap( testString ).toString() == "one two three\nfour" ); - CHECK( Catch::LineWrapper().setRight( 13 ).wrap( testString ).toString() == "one two\nthree four" ); + 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", "" ) { - CHECK( Catch::LineWrapper().setRight( 9 ).wrap( testString ).toString() == "one two\nthree\nfour" ); - CHECK( Catch::LineWrapper().setRight( 8 ).wrap( testString ).toString() == "one two\nthree\nfour" ); + 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", "" ) { - CHECK( Catch::LineWrapper().setRight( 7 ).wrap( testString ).toString() == "one\ntwo\nthree\nfour" ); - CHECK( Catch::LineWrapper().setRight( 5 ).wrap( testString ).toString() == "one\ntwo\nthree\nfour" ); + 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", "" ) { - CHECK( Catch::LineWrapper().setRight( 4 ).wrap( "abcdef" ).toString() == "abc-\ndef" ); - CHECK( Catch::LineWrapper().setRight( 4 ).wrap( "abcdefg" ).toString() == "abc-\ndefg" ); - CHECK( Catch::LineWrapper().setRight( 4 ).wrap("abcdefgh" ).toString() == "abc-\ndef-\ngh" ); + 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" ); - CHECK( Catch::LineWrapper().setRight( 4 ).wrap( testString ).toString() == "one\ntwo\nthr-\nee\nfour" ); - CHECK( Catch::LineWrapper().setRight( 3 ).wrap( testString ).toString() == "one\ntwo\nth-\nree\nfo-\nur" ); + 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", "" ) { - Catch::LineWrapper wrapper; - wrapper.setRight( 7 ).wrap( testString ); - CHECK( wrapper.size() == 4 ); - CHECK( wrapper[0] == "one" ); - CHECK( wrapper[1] == "two" ); - CHECK( wrapper[2] == "three" ); - CHECK( wrapper[3] == "four" ); + Text text( testString, TextAttributes().setWidth( 6 ) ); + REQUIRE( text.size() == 4 ); + CHECK( text[0] == "one" ); + CHECK( text[1] == "two" ); + CHECK( text[2] == "three" ); + CHECK( text[3] == "four" ); } SECTION( "Indent first line differently", "" ) { - CHECK( Catch::LineWrapper() - .setRight( 10 ) - .setIndent( 4 ) - .setInitialIndent( 1 ) - .wrap( testString ).toString() == " one two\n three\n four" ); + Text text( testString, TextAttributes() + .setWidth( 10 ) + .setIndent( 4 ) + .setInitialIndent( 1 ) ); + CHECK( text.toString() == " one two\n three\n four" ); } } @@ -483,25 +485,35 @@ TEST_CASE( "Long strings can be wrapped", "[wrap]" ) { std::string testString = "one two\nthree four"; SECTION( "No wrapping" , "" ) { - CHECK( Catch::LineWrapper().setRight( 80 ).wrap( testString ).toString() == testString ); - CHECK( Catch::LineWrapper().setRight( 18 ).wrap( testString ).toString() == testString ); - CHECK( Catch::LineWrapper().setRight( 10 ).wrap( testString ).toString() == testString ); + 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" , "" ) { - CHECK( Catch::LineWrapper().setRight( 10 ).wrap( "abcdef\n" ).toString() == "abcdef\n" ); - CHECK( Catch::LineWrapper().setRight( 6 ).wrap( "abcdef" ).toString() == "abcdef" ); - CHECK( Catch::LineWrapper().setRight( 6 ).wrap( "abcdef\n" ).toString() == "abcdef\n" ); + CHECK( Text( "abcdef\n", TextAttributes().setWidth( 10 ) ).toString() == "abcdef\n" ); + CHECK( Text( "abcdef", TextAttributes().setWidth( 6 ) ).toString() == "abcdef" ); + CHECK( Text( "abcdef\n", TextAttributes().setWidth( 6 ) ).toString() == "abcdef\n" ); } SECTION( "Wrapped once", "" ) { - CHECK( Catch::LineWrapper().setRight( 9 ).wrap( testString ).toString() == "one two\nthree\nfour" ); - CHECK( Catch::LineWrapper().setRight( 8 ).wrap( testString ).toString() == "one two\nthree\nfour" ); - CHECK( Catch::LineWrapper().setRight( 7 ).wrap( testString ).toString() == "one two\nthree\nfour" ); + 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", "" ) { - CHECK( Catch::LineWrapper().setRight( 6 ).wrap( testString ).toString() == "one\ntwo\nthree\nfour" ); + CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" ); } } + SECTION( "With tabs", "" ) { + + // guide: 1234567890123456789 + std::string testString = "one two /tthree four five six"; + + CHECK( Text( testString, TextAttributes().setWidth( 18 ) ).toString() + == "one two three/n four/n five/n six" ); + } + + } using namespace Catch; @@ -520,7 +532,7 @@ public: std::size_t fromIndex; std::size_t toIndex; }; - + ColourString( std::string const& _string ) : string( _string ) {} @@ -576,34 +588,8 @@ private: std::vector colours; }; -class Text -{ -public: - Text( std::string const& _string ) : originalString( _string ) { - } - Text( char const* const _string ) : originalString( _string ) { - } - - friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { - _text.print( _stream ); - return _stream; - } - -private: - void process() const { - - } - void print( std::ostream& stream ) const { - stream << originalString; - } - - std::string originalString; -// std::vector< -}; - +// !TBD: This will be folded into Text class TEST_CASE( "Strings can be rendered with colour", "[colour]" ) { -// Text text = "`red`This is in red. `green` this is in green"; -// std::cout << text << std::endl; { ColourString cs( "hello" ); @@ -621,3 +607,14 @@ TEST_CASE( "Strings can be rendered with colour", "[colour]" ) { } } + +TEST_CASE( "Text can be formatted using the Text class", "" ) { + Text text( "hi there" ); + + CHECK( Text( "hi there" ).toString() == "hi there" ); + + TextAttributes narrow; + narrow.setWidth( 6 ); + + CHECK( Text( "hi there", narrow ).toString() == "hi\nthere" ); +} diff --git a/projects/XCode4/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj b/projects/XCode4/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj index d54036ab..347234fb 100644 --- a/projects/XCode4/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj +++ b/projects/XCode4/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj @@ -67,6 +67,7 @@ 2694A1F8169FFF9B004816E3 /* catch_line_wrap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_line_wrap.h; sourceTree = ""; }; 2694A1FA169FFFEC004816E3 /* catch_line_wrap.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = catch_line_wrap.hpp; sourceTree = ""; }; 2694A1FB16A0000E004816E3 /* catch_line_wrap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = catch_line_wrap.cpp; sourceTree = ""; }; + 26DACF2F17206D3400A21326 /* catch_text.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_text.h; sourceTree = ""; }; 4A084F1C15DACEEA0027E631 /* catch_test_case_info.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = catch_test_case_info.hpp; sourceTree = ""; }; 4A084F1D15DAD15F0027E631 /* catch_test_spec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_test_spec.h; sourceTree = ""; }; 4A3D7DD01503869D005F9203 /* catch_matchers.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = catch_matchers.hpp; sourceTree = ""; }; @@ -399,6 +400,7 @@ 2694A1F8169FFF9B004816E3 /* catch_line_wrap.h */, 26759472171C72A400A84BD1 /* catch_sfinae.hpp */, 26759473171C74C200A84BD1 /* catch_compiler_capabilities.h */, + 26DACF2F17206D3400A21326 /* catch_text.h */, ); name = Infrastructure; sourceTree = ""; @@ -522,7 +524,7 @@ GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_VERSION = ""; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; @@ -564,7 +566,7 @@ GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_VERSION = ""; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; GCC_WARN_ABOUT_MISSING_NEWLINE = YES;