From e7aa432850c34557e857d8675bff9b3f03de41b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Sat, 1 Aug 2020 20:32:26 +0200 Subject: [PATCH] Split TextFlow out from Clara Now that it has its own header, various reporter TUs that want to format text do not have to also include Clara. Together with outlining implementations from a header into a separate TU, this has noticeably improved the compilation times of the testing impl. As part of this split, I also implemented some improvements to the TextFlow code in comparison to the upstream code. These are: * Replaced the `Spacer` type with a free function that constructs special `Column` that does the same thing. * Generic performance improvements, such as eliminating needless allocations, reserving space in needed allocations, and using smarter algorithms in some places. * Because `Column` only ever stored 1 string in its vector, it now holds the string directly instead. --- src/CMakeLists.txt | 3 +- src/catch2/catch_all.hpp | 2 +- src/catch2/catch_session.cpp | 6 +- .../interfaces/catch_interfaces_reporter.cpp | 18 +- src/catch2/internal/catch_clara.hpp | 1 - src/catch2/internal/catch_clara_upstream.hpp | 366 +----------------- src/catch2/internal/catch_text.hpp | 17 - src/catch2/internal/catch_textflow.cpp | 235 +++++++++++ src/catch2/internal/catch_textflow.hpp | 144 +++++++ .../reporters/catch_reporter_console.cpp | 17 +- src/catch2/reporters/catch_reporter_junit.cpp | 4 +- .../reporters/catch_reporter_teamcity.cpp | 4 +- 12 files changed, 418 insertions(+), 399 deletions(-) delete mode 100644 src/catch2/internal/catch_text.hpp create mode 100644 src/catch2/internal/catch_textflow.cpp create mode 100644 src/catch2/internal/catch_textflow.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 47e151df..5165d37d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -119,7 +119,7 @@ set(INTERNAL_HEADERS ${SOURCES_DIR}/internal/catch_test_registry.hpp ${SOURCES_DIR}/catch_test_spec.hpp ${SOURCES_DIR}/internal/catch_test_spec_parser.hpp - ${SOURCES_DIR}/internal/catch_text.hpp + ${SOURCES_DIR}/internal/catch_textflow.hpp ${SOURCES_DIR}/catch_timer.hpp ${SOURCES_DIR}/internal/catch_to_string.hpp ${SOURCES_DIR}/catch_tostring.hpp @@ -175,6 +175,7 @@ set(IMPL_SOURCES ${SOURCES_DIR}/internal/catch_test_case_registry_impl.cpp ${SOURCES_DIR}/internal/catch_test_case_tracker.cpp ${SOURCES_DIR}/internal/catch_test_registry.cpp + ${SOURCES_DIR}/internal/catch_textflow.cpp ${SOURCES_DIR}/catch_test_spec.cpp ${SOURCES_DIR}/internal/catch_test_spec_parser.cpp ${SOURCES_DIR}/catch_timer.cpp diff --git a/src/catch2/catch_all.hpp b/src/catch2/catch_all.hpp index 5f8d32dc..508f38db 100644 --- a/src/catch2/catch_all.hpp +++ b/src/catch2/catch_all.hpp @@ -83,7 +83,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/catch2/catch_session.cpp b/src/catch2/catch_session.cpp index 80767184..05cf6bd1 100644 --- a/src/catch2/catch_session.cpp +++ b/src/catch2/catch_session.cpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include @@ -144,7 +144,7 @@ namespace Catch { try { std::rethrow_exception(ex_ptr); } catch ( std::exception const& ex ) { - Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; + Catch::cerr() << TextFlow::Column( ex.what() ).indent(2) << '\n'; } } } @@ -182,7 +182,7 @@ namespace Catch { Catch::cerr() << Colour( Colour::Red ) << "\nError(s) in input:\n" - << Column( result.errorMessage() ).indent( 2 ) + << TextFlow::Column( result.errorMessage() ).indent( 2 ) << "\n\n"; Catch::cerr() << "Run with -? for usage\n" << std::endl; return MaxExitCode; diff --git a/src/catch2/interfaces/catch_interfaces_reporter.cpp b/src/catch2/interfaces/catch_interfaces_reporter.cpp index 3d1a01c5..7e4dd9ef 100644 --- a/src/catch2/interfaces/catch_interfaces_reporter.cpp +++ b/src/catch2/interfaces/catch_interfaces_reporter.cpp @@ -9,11 +9,13 @@ #include #include #include +#include #include #include -#include +#include #include #include +#include #include #include @@ -117,15 +119,15 @@ namespace Catch { for (auto const& desc : descriptions) { if (config.verbosity() == Verbosity::Quiet) { Catch::cout() - << Column(desc.name) + << TextFlow::Column(desc.name) .indent(2) .width(5 + maxNameLen) << '\n'; } else { Catch::cout() - << Column(desc.name + ":") + << TextFlow::Column(desc.name + ":") .indent(2) .width(5 + maxNameLen) - + Column(desc.description) + + TextFlow::Column(desc.description) .initialIndent(0) .indent(2) .width(CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8) @@ -149,12 +151,12 @@ namespace Catch { : Colour::None; Colour colourGuard(colour); - Catch::cout() << Column(testCaseInfo.name).initialIndent(2).indent(4) << '\n'; + Catch::cout() << TextFlow::Column(testCaseInfo.name).initialIndent(2).indent(4) << '\n'; if (config.verbosity() >= Verbosity::High) { - Catch::cout() << Column(Catch::Detail::stringify(testCaseInfo.lineInfo)).indent(4) << std::endl; + Catch::cout() << TextFlow::Column(Catch::Detail::stringify(testCaseInfo.lineInfo)).indent(4) << std::endl; } if (!testCaseInfo.tags.empty() && config.verbosity() > Verbosity::Quiet) { - Catch::cout() << Column(testCaseInfo.tagsAsString()).indent(6) << '\n'; + Catch::cout() << TextFlow::Column(testCaseInfo.tagsAsString()).indent(6) << '\n'; } } @@ -176,7 +178,7 @@ namespace Catch { ReusableStringStream rss; rss << " " << std::setw(2) << tagCount.count << " "; auto str = rss.str(); - auto wrapper = Column(tagCount.all()) + auto wrapper = TextFlow::Column(tagCount.all()) .initialIndent(0) .indent(str.size()) .width(CATCH_CONFIG_CONSOLE_WIDTH - 10); diff --git a/src/catch2/internal/catch_clara.hpp b/src/catch2/internal/catch_clara.hpp index 19db5492..927813b6 100644 --- a/src/catch2/internal/catch_clara.hpp +++ b/src/catch2/internal/catch_clara.hpp @@ -22,7 +22,6 @@ #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wweak-vtables" - #pragma clang diagnostic ignored "-Wexit-time-destructors" #pragma clang diagnostic ignored "-Wshadow" #pragma clang diagnostic ignored "-Wdeprecated" #endif diff --git a/src/catch2/internal/catch_clara_upstream.hpp b/src/catch2/internal/catch_clara_upstream.hpp index 64b762d0..76263b53 100644 --- a/src/catch2/internal/catch_clara_upstream.hpp +++ b/src/catch2/internal/catch_clara_upstream.hpp @@ -14,10 +14,6 @@ #define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 #endif -#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH -#endif - #ifndef CLARA_CONFIG_OPTIONAL_TYPE #ifdef __has_include #if __has_include() && __cplusplus >= 201703L @@ -28,356 +24,16 @@ #endif -// ----------- #included from clara_textflow.hpp ----------- +#include -// TextFlowCpp -// -// A single-header library for wrapping and laying out basic text, by Phil Nash -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// This project is hosted at https://github.com/philsquared/textflowcpp - -#ifndef CATCH_CLARA_TEXTFLOW_HPP_INCLUDED -#define CATCH_CLARA_TEXTFLOW_HPP_INCLUDED - -#include -#include -#include -#include - -#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 -#endif - - -namespace Catch { -namespace clara { -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 = CATCH_CLARA_TEXTFLOW_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 = 0; - size_t m_end = 0; - 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(); - m_end = m_pos; - if (line()[m_pos] == '\n') { - ++m_end; - } - while (m_end < line().size() && line()[m_end] != '\n') - ++m_end; - - if (m_end < m_pos + width) { - m_len = m_end - 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: - using difference_type = std::ptrdiff_t; - using value_type = std::string; - using pointer = value_type * ; - using reference = value_type & ; - using iterator_category = std::forward_iterator_tag; - - 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 <= m_end); - return addIndentAndSuffix(line().substr(m_pos, m_len)); - } - - auto operator ++() -> iterator& { - m_pos += m_len; - if (m_pos < line().size() && line()[m_pos] == '\n') - m_pos += 1; - else - 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 { *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: - explicit 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: - using difference_type = std::ptrdiff_t; - using value_type = std::string; - using pointer = value_type * ; - using reference = value_type & ; - using iterator_category = std::forward_iterator_tag; - - explicit 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 { *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; -} -} - -} -} -#endif // CATCH_CLARA_TEXTFLOW_HPP_INCLUDED - -// ----------- end of #include from clara_textflow.hpp ----------- // ........... back in clara.hpp #include #include #include -#include #include +#include +#include #if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) #define CATCH_PLATFORM_WINDOWS @@ -989,7 +645,7 @@ namespace detail { oss << opt; } if( !m_hint.empty() ) - oss << " <" << m_hint << ">"; + oss << " <" << m_hint << '>'; return { { oss.str(), m_description } }; } @@ -1122,26 +778,26 @@ namespace detail { void writeToStream( std::ostream &os ) const { if (!m_exeName.name().empty()) { - os << "usage:\n" << " " << m_exeName.name() << " "; + os << "usage:\n" << " " << m_exeName.name() << ' '; bool required = true, first = true; for( auto const &arg : m_args ) { if (first) first = false; else - os << " "; + os << ' '; if( arg.isOptional() && required ) { - os << "["; + os << '['; required = false; } - os << "<" << arg.hint() << ">"; + os << '<' << arg.hint() << '>'; if( arg.cardinality() == 0 ) os << " ... "; } if( !required ) - os << "]"; + os << ']'; if( !m_options.empty() ) os << " options"; - os << "\n\nwhere options are:" << std::endl; + os << "\n\nwhere options are:\n"; } auto rows = getHelpColumns(); @@ -1157,7 +813,7 @@ namespace detail { TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + TextFlow::Spacer(4) + TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); - os << row << std::endl; + os << row << '\n'; } } diff --git a/src/catch2/internal/catch_text.hpp b/src/catch2/internal/catch_text.hpp deleted file mode 100644 index 7a2c2923..00000000 --- a/src/catch2/internal/catch_text.hpp +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Created by Phil on 10/2/2014. - * Copyright 2014 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 - -namespace Catch { - using namespace clara::TextFlow; -} - -#endif // TWOBLUECUBES_CATCH_TEXT_H_INCLUDED diff --git a/src/catch2/internal/catch_textflow.cpp b/src/catch2/internal/catch_textflow.cpp new file mode 100644 index 00000000..c65a6eb9 --- /dev/null +++ b/src/catch2/internal/catch_textflow.cpp @@ -0,0 +1,235 @@ +#include +#include +#include + +namespace { + bool isWhitespace( char c ) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + } + + bool isBreakableBefore( char c ) { + static const char chars[] = "[({<|"; + return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr; + } + + bool isBreakableAfter( char c ) { + static const char chars[] = "])}>.,:;*+-=&/\\"; + return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr; + } + + bool isBoundary( std::string const& line, size_t at ) { + 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] ); + } + +} // namespace + +namespace Catch { + namespace TextFlow { + + void Column::iterator::calcLength() { + m_suffix = false; + auto width = m_column.m_width - indent(); + m_end = m_pos; + std::string const& current_line = m_column.m_string; + if ( current_line[m_pos] == '\n' ) { + ++m_end; + } + while ( m_end < current_line.size() && + current_line[m_end] != '\n' ) { + ++m_end; + } + + if ( m_end < m_pos + width ) { + m_len = m_end - m_pos; + } else { + size_t len = width; + while ( len > 0 && !isBoundary( current_line, m_pos + len ) ) { + --len; + } + while ( len > 0 && + isWhitespace( current_line[m_pos + len - 1] ) ) { + --len; + } + + if ( len > 0 ) { + m_len = len; + } else { + m_suffix = true; + m_len = width - 1; + } + } + } + + size_t Column::iterator::indent() const { + auto initial = + m_pos == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + std::string + Column::iterator::addIndentAndSuffix( size_t position, + size_t length ) const { + std::string ret; + const auto desired_indent = indent(); + ret.reserve( desired_indent + length + m_suffix ); + ret.append( desired_indent, ' ' ); + ret.append( m_column.m_string, position, length ); + if ( m_suffix ) { + ret.push_back( '-' ); + } + + return ret; + } + + Column::iterator::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_pos = m_column.m_string.size(); + } + } + + std::string Column::iterator::operator*() const { + assert( m_pos <= m_end ); + return addIndentAndSuffix( m_pos, m_len ); + } + + Column::iterator& Column::iterator::operator++() { + m_pos += m_len; + std::string const& current_line = m_column.m_string; + if ( m_pos < current_line.size() && current_line[m_pos] == '\n' ) { + m_pos += 1; + } else { + while ( m_pos < current_line.size() && + isWhitespace( current_line[m_pos] ) ) { + ++m_pos; + } + } + + if ( m_pos != current_line.size() ) { + calcLength(); + } + return *this; + } + + Column::iterator Column::iterator::operator++( int ) { + iterator prev( *this ); + operator++(); + return prev; + } + + 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; + } + + Column Spacer( size_t spaceWidth ) { + Column ret{ "" }; + ret.width( spaceWidth ); + return ret; + } + + Columns::iterator::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() ); + } + } + + Columns::iterator::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() ); + } + } + + std::string Columns::iterator::operator*() const { + std::string row, padding; + + for ( size_t i = 0; i < m_columns.size(); ++i ) { + const auto width = m_columns[i].width(); + if ( m_iterators[i] != m_columns[i].end() ) { + std::string col = *m_iterators[i]; + row += padding; + row += col; + + padding.clear(); + if ( col.size() < width ) { + padding.append( width - col.size(), ' ' ); + } + } else { + padding.append( width, ' ' ); + } + } + return row; + } + + Columns::iterator& Columns::iterator::operator++() { + for ( size_t i = 0; i < m_columns.size(); ++i ) { + if ( m_iterators[i] != m_columns[i].end() ) { + ++m_iterators[i]; + } + } + return *this; + } + + Columns::iterator Columns::iterator::operator++( int ) { + iterator prev( *this ); + operator++(); + return prev; + } + + 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; + } + + Columns Column::operator+( Column const& other ) { + Columns cols; + cols += *this; + cols += other; + return cols; + } + + Columns& Columns::operator+=( Column const& col ) { + m_columns.push_back( col ); + return *this; + } + + Columns Columns::operator+( Column const& col ) { + Columns combined = *this; + combined += col; + return combined; + } + + } // namespace TextFlow +} // namespace Catch diff --git a/src/catch2/internal/catch_textflow.hpp b/src/catch2/internal/catch_textflow.hpp new file mode 100644 index 00000000..27f5184b --- /dev/null +++ b/src/catch2/internal/catch_textflow.hpp @@ -0,0 +1,144 @@ +#ifndef CATCH_CLARA_TEXTFLOW_HPP_INCLUDED +#define CATCH_CLARA_TEXTFLOW_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + namespace TextFlow { + + class Columns; + + class Column { + std::string m_string; + size_t m_width = CATCH_CONFIG_CONSOLE_WIDTH - 1; + size_t m_indent = 0; + size_t m_initialIndent = std::string::npos; + + public: + class iterator { + friend Column; + struct EndTag {}; + + Column const& m_column; + size_t m_pos = 0; + + size_t m_len = 0; + size_t m_end = 0; + bool m_suffix = false; + + iterator( Column const& column, EndTag ): + m_column( column ), m_pos( m_column.m_string.size() ) {} + + void calcLength(); + + // Returns current indention width + size_t indent() const; + + // Creates an indented and (optionally) suffixed string from + // current iterator position, indentation and length. + std::string addIndentAndSuffix( size_t position, + size_t length ) const; + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::forward_iterator_tag; + + explicit iterator( Column const& column ); + + std::string operator*() const; + + iterator& operator++(); + iterator operator++( int ); + + bool operator==( iterator const& other ) const { + return m_pos == other.m_pos && &m_column == &other.m_column; + } + bool operator!=( iterator const& other ) const { + return !operator==( other ); + } + }; + using const_iterator = iterator; + + explicit Column( std::string const& text ): m_string( text ) {} + + Column& width( size_t newWidth ) { + assert( newWidth > 0 ); + m_width = newWidth; + return *this; + } + Column& indent( size_t newIndent ) { + m_indent = newIndent; + return *this; + } + Column& initialIndent( size_t newIndent ) { + m_initialIndent = newIndent; + return *this; + } + + size_t width() const { return m_width; } + iterator begin() const { return iterator( *this ); } + iterator end() const { return { *this, iterator::EndTag{} }; } + + friend std::ostream& operator<<( std::ostream& os, + Column const& col ); + + Columns operator+( Column const& other ); + }; + + //! Creates a column that serves as an empty space of specific width + Column Spacer( size_t 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 ); + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::forward_iterator_tag; + + explicit iterator( Columns const& columns ); + + 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; + } + std::string operator*() const; + iterator& operator++(); + iterator operator++( int ); + }; + using const_iterator = iterator; + + iterator begin() const { return iterator( *this ); } + iterator end() const { return { *this, iterator::EndTag() }; } + + Columns& operator+=( Column const& col ); + Columns operator+( Column const& col ); + + friend std::ostream& operator<<( std::ostream& os, + Columns const& cols ); + }; + + } // namespace TextFlow +} // namespace Catch +#endif // CATCH_CLARA_TEXTFLOW_HPP_INCLUDED diff --git a/src/catch2/reporters/catch_reporter_console.cpp b/src/catch2/reporters/catch_reporter_console.cpp index afceede4..2924c920 100644 --- a/src/catch2/reporters/catch_reporter_console.cpp +++ b/src/catch2/reporters/catch_reporter_console.cpp @@ -14,12 +14,11 @@ #include #include #include -#include +#include #include #include #include #include -#include #include #include @@ -152,7 +151,7 @@ private: if (result.hasExpandedExpression()) { stream << "with expansion:\n"; Colour colourGuard(Colour::ReconstructedExpression); - stream << Column(result.getExpandedExpression()).indent(2) << '\n'; + stream << TextFlow::Column(result.getExpandedExpression()).indent(2) << '\n'; } } void printMessage() const { @@ -161,7 +160,7 @@ private: for (auto const& msg : messages) { // If this assertion is a warning ignore any INFO messages if (printInfoMessages || msg.type != ResultWas::Info) - stream << Column(msg.message).indent(2) << '\n'; + stream << TextFlow::Column(msg.message).indent(2) << '\n'; } } void printSourceInfo() const { @@ -297,10 +296,10 @@ public: m_isOpen = true; *this << RowBreak(); - Columns headerCols; - Spacer spacer(2); + TextFlow::Columns headerCols; + auto spacer = TextFlow::Spacer(2); for (auto const& info : m_columnInfos) { - headerCols += Column(info.name).width(static_cast(info.width - 2)); + headerCols += TextFlow::Column(info.name).width(static_cast(info.width - 2)); headerCols += spacer; } m_os << headerCols << '\n'; @@ -438,7 +437,7 @@ void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { void ConsoleReporter::benchmarkPreparing(std::string const& name) { lazyPrintWithoutClosingBenchmarkTable(); - auto nameCol = Column(name).width(static_cast(m_tablePrinter->columnInfos()[0].width - 2)); + auto nameCol = TextFlow::Column(name).width(static_cast(m_tablePrinter->columnInfos()[0].width - 2)); bool firstLine = true; for (auto line : nameCol) { @@ -585,7 +584,7 @@ void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t i += 2; else i = 0; - stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n'; + stream << TextFlow::Column(_string).indent(indent + i).initialIndent(indent) << '\n'; } struct SummaryColumn { diff --git a/src/catch2/reporters/catch_reporter_junit.cpp b/src/catch2/reporters/catch_reporter_junit.cpp index 0f171e05..3b8e2e9a 100644 --- a/src/catch2/reporters/catch_reporter_junit.cpp +++ b/src/catch2/reporters/catch_reporter_junit.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include @@ -266,7 +266,7 @@ namespace Catch { } if (result.hasExpandedExpression()) { rss << "with expansion:\n"; - rss << Column(result.getExpandedExpression()).indent(2) << '\n'; + rss << TextFlow::Column(result.getExpandedExpression()).indent(2) << '\n'; } } else { rss << '\n'; diff --git a/src/catch2/reporters/catch_reporter_teamcity.cpp b/src/catch2/reporters/catch_reporter_teamcity.cpp index 5634971d..dd60cf9e 100644 --- a/src/catch2/reporters/catch_reporter_teamcity.cpp +++ b/src/catch2/reporters/catch_reporter_teamcity.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include @@ -23,7 +23,7 @@ namespace Catch { i += 2; else i = 0; - os << Column(_string) + os << TextFlow::Column(_string) .indent(indent + i) .initialIndent(indent) << '\n'; }