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.
This commit is contained in:
Martin Hořeňovský 2020-08-01 20:32:26 +02:00
parent 81aa2d5582
commit e7aa432850
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
12 changed files with 418 additions and 399 deletions

View File

@ -119,7 +119,7 @@ set(INTERNAL_HEADERS
${SOURCES_DIR}/internal/catch_test_registry.hpp ${SOURCES_DIR}/internal/catch_test_registry.hpp
${SOURCES_DIR}/catch_test_spec.hpp ${SOURCES_DIR}/catch_test_spec.hpp
${SOURCES_DIR}/internal/catch_test_spec_parser.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}/catch_timer.hpp
${SOURCES_DIR}/internal/catch_to_string.hpp ${SOURCES_DIR}/internal/catch_to_string.hpp
${SOURCES_DIR}/catch_tostring.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_registry_impl.cpp
${SOURCES_DIR}/internal/catch_test_case_tracker.cpp ${SOURCES_DIR}/internal/catch_test_case_tracker.cpp
${SOURCES_DIR}/internal/catch_test_registry.cpp ${SOURCES_DIR}/internal/catch_test_registry.cpp
${SOURCES_DIR}/internal/catch_textflow.cpp
${SOURCES_DIR}/catch_test_spec.cpp ${SOURCES_DIR}/catch_test_spec.cpp
${SOURCES_DIR}/internal/catch_test_spec_parser.cpp ${SOURCES_DIR}/internal/catch_test_spec_parser.cpp
${SOURCES_DIR}/catch_timer.cpp ${SOURCES_DIR}/catch_timer.cpp

View File

@ -83,7 +83,7 @@
#include <catch2/internal/catch_test_macro_impl.hpp> #include <catch2/internal/catch_test_macro_impl.hpp>
#include <catch2/internal/catch_test_registry.hpp> #include <catch2/internal/catch_test_registry.hpp>
#include <catch2/internal/catch_test_spec_parser.hpp> #include <catch2/internal/catch_test_spec_parser.hpp>
#include <catch2/internal/catch_text.hpp> #include <catch2/internal/catch_textflow.hpp>
#include <catch2/internal/catch_to_string.hpp> #include <catch2/internal/catch_to_string.hpp>
#include <catch2/internal/catch_uncaught_exceptions.hpp> #include <catch2/internal/catch_uncaught_exceptions.hpp>
#include <catch2/internal/catch_unique_ptr.hpp> #include <catch2/internal/catch_unique_ptr.hpp>

View File

@ -17,7 +17,7 @@
#include <catch2/interfaces/catch_interfaces_reporter.hpp> #include <catch2/interfaces/catch_interfaces_reporter.hpp>
#include <catch2/internal/catch_random_number_generator.hpp> #include <catch2/internal/catch_random_number_generator.hpp>
#include <catch2/internal/catch_startup_exception_registry.hpp> #include <catch2/internal/catch_startup_exception_registry.hpp>
#include <catch2/internal/catch_text.hpp> #include <catch2/internal/catch_textflow.hpp>
#include <catch2/internal/catch_windows_h_proxy.hpp> #include <catch2/internal/catch_windows_h_proxy.hpp>
#include <catch2/reporters/catch_reporter_listening.hpp> #include <catch2/reporters/catch_reporter_listening.hpp>
@ -144,7 +144,7 @@ namespace Catch {
try { try {
std::rethrow_exception(ex_ptr); std::rethrow_exception(ex_ptr);
} catch ( std::exception const& ex ) { } 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() Catch::cerr()
<< Colour( Colour::Red ) << Colour( Colour::Red )
<< "\nError(s) in input:\n" << "\nError(s) in input:\n"
<< Column( result.errorMessage() ).indent( 2 ) << TextFlow::Column( result.errorMessage() ).indent( 2 )
<< "\n\n"; << "\n\n";
Catch::cerr() << "Run with -? for usage\n" << std::endl; Catch::cerr() << "Run with -? for usage\n" << std::endl;
return MaxExitCode; return MaxExitCode;

View File

@ -9,11 +9,13 @@
#include <catch2/interfaces/catch_interfaces_config.hpp> #include <catch2/interfaces/catch_interfaces_config.hpp>
#include <catch2/catch_config.hpp> #include <catch2/catch_config.hpp>
#include <catch2/internal/catch_console_colour.hpp> #include <catch2/internal/catch_console_colour.hpp>
#include <catch2/internal/catch_console_width.hpp>
#include <catch2/catch_message.hpp> #include <catch2/catch_message.hpp>
#include <catch2/internal/catch_list.hpp> #include <catch2/internal/catch_list.hpp>
#include <catch2/internal/catch_text.hpp> #include <catch2/internal/catch_textflow.hpp>
#include <catch2/internal/catch_string_manip.hpp> #include <catch2/internal/catch_string_manip.hpp>
#include <catch2/catch_test_case_info.hpp> #include <catch2/catch_test_case_info.hpp>
#include <catch2/internal/catch_textflow.hpp>
#include <algorithm> #include <algorithm>
#include <iomanip> #include <iomanip>
@ -117,15 +119,15 @@ namespace Catch {
for (auto const& desc : descriptions) { for (auto const& desc : descriptions) {
if (config.verbosity() == Verbosity::Quiet) { if (config.verbosity() == Verbosity::Quiet) {
Catch::cout() Catch::cout()
<< Column(desc.name) << TextFlow::Column(desc.name)
.indent(2) .indent(2)
.width(5 + maxNameLen) << '\n'; .width(5 + maxNameLen) << '\n';
} else { } else {
Catch::cout() Catch::cout()
<< Column(desc.name + ":") << TextFlow::Column(desc.name + ":")
.indent(2) .indent(2)
.width(5 + maxNameLen) .width(5 + maxNameLen)
+ Column(desc.description) + TextFlow::Column(desc.description)
.initialIndent(0) .initialIndent(0)
.indent(2) .indent(2)
.width(CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8) .width(CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8)
@ -149,12 +151,12 @@ namespace Catch {
: Colour::None; : Colour::None;
Colour colourGuard(colour); 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) { 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) { 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; ReusableStringStream rss;
rss << " " << std::setw(2) << tagCount.count << " "; rss << " " << std::setw(2) << tagCount.count << " ";
auto str = rss.str(); auto str = rss.str();
auto wrapper = Column(tagCount.all()) auto wrapper = TextFlow::Column(tagCount.all())
.initialIndent(0) .initialIndent(0)
.indent(str.size()) .indent(str.size())
.width(CATCH_CONFIG_CONSOLE_WIDTH - 10); .width(CATCH_CONFIG_CONSOLE_WIDTH - 10);

View File

@ -22,7 +22,6 @@
#if defined(__clang__) #if defined(__clang__)
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wweak-vtables" #pragma clang diagnostic ignored "-Wweak-vtables"
#pragma clang diagnostic ignored "-Wexit-time-destructors"
#pragma clang diagnostic ignored "-Wshadow" #pragma clang diagnostic ignored "-Wshadow"
#pragma clang diagnostic ignored "-Wdeprecated" #pragma clang diagnostic ignored "-Wdeprecated"
#endif #endif

View File

@ -14,10 +14,6 @@
#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 #define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80
#endif #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 #ifndef CLARA_CONFIG_OPTIONAL_TYPE
#ifdef __has_include #ifdef __has_include
#if __has_include(<optional>) && __cplusplus >= 201703L #if __has_include(<optional>) && __cplusplus >= 201703L
@ -28,356 +24,16 @@
#endif #endif
// ----------- #included from clara_textflow.hpp ----------- #include <catch2/internal/catch_textflow.hpp>
// 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 <cassert>
#include <ostream>
#include <sstream>
#include <vector>
#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<std::string> 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<Column> m_columns;
public:
class iterator {
friend Columns;
struct EndTag {};
std::vector<Column> const& m_columns;
std::vector<Column::iterator> 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 // ........... back in clara.hpp
#include <cctype> #include <cctype>
#include <string> #include <string>
#include <memory> #include <memory>
#include <set>
#include <algorithm> #include <algorithm>
#include <ostream>
#include <sstream>
#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) #if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) )
#define CATCH_PLATFORM_WINDOWS #define CATCH_PLATFORM_WINDOWS
@ -989,7 +645,7 @@ namespace detail {
oss << opt; oss << opt;
} }
if( !m_hint.empty() ) if( !m_hint.empty() )
oss << " <" << m_hint << ">"; oss << " <" << m_hint << '>';
return { { oss.str(), m_description } }; return { { oss.str(), m_description } };
} }
@ -1122,26 +778,26 @@ namespace detail {
void writeToStream( std::ostream &os ) const { void writeToStream( std::ostream &os ) const {
if (!m_exeName.name().empty()) { if (!m_exeName.name().empty()) {
os << "usage:\n" << " " << m_exeName.name() << " "; os << "usage:\n" << " " << m_exeName.name() << ' ';
bool required = true, first = true; bool required = true, first = true;
for( auto const &arg : m_args ) { for( auto const &arg : m_args ) {
if (first) if (first)
first = false; first = false;
else else
os << " "; os << ' ';
if( arg.isOptional() && required ) { if( arg.isOptional() && required ) {
os << "["; os << '[';
required = false; required = false;
} }
os << "<" << arg.hint() << ">"; os << '<' << arg.hint() << '>';
if( arg.cardinality() == 0 ) if( arg.cardinality() == 0 )
os << " ... "; os << " ... ";
} }
if( !required ) if( !required )
os << "]"; os << ']';
if( !m_options.empty() ) if( !m_options.empty() )
os << " options"; os << " options";
os << "\n\nwhere options are:" << std::endl; os << "\n\nwhere options are:\n";
} }
auto rows = getHelpColumns(); auto rows = getHelpColumns();
@ -1157,7 +813,7 @@ namespace detail {
TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) +
TextFlow::Spacer(4) + TextFlow::Spacer(4) +
TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth );
os << row << std::endl; os << row << '\n';
} }
} }

View File

@ -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 <catch2/internal/catch_clara.hpp>
namespace Catch {
using namespace clara::TextFlow;
}
#endif // TWOBLUECUBES_CATCH_TEXT_H_INCLUDED

View File

@ -0,0 +1,235 @@
#include <catch2/internal/catch_textflow.hpp>
#include <cstring>
#include <ostream>
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

View File

@ -0,0 +1,144 @@
#ifndef CATCH_CLARA_TEXTFLOW_HPP_INCLUDED
#define CATCH_CLARA_TEXTFLOW_HPP_INCLUDED
#include <cassert>
#include <catch2/internal/catch_console_width.hpp>
#include <string>
#include <vector>
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<Column> m_columns;
public:
class iterator {
friend Columns;
struct EndTag {};
std::vector<Column> const& m_columns;
std::vector<Column::iterator> 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

View File

@ -14,12 +14,11 @@
#include <catch2/internal/catch_console_colour.hpp> #include <catch2/internal/catch_console_colour.hpp>
#include <catch2/internal/catch_string_manip.hpp> #include <catch2/internal/catch_string_manip.hpp>
#include <catch2/catch_version.hpp> #include <catch2/catch_version.hpp>
#include <catch2/internal/catch_text.hpp> #include <catch2/internal/catch_textflow.hpp>
#include <catch2/internal/catch_stream.hpp> #include <catch2/internal/catch_stream.hpp>
#include <catch2/internal/catch_stringref.hpp> #include <catch2/internal/catch_stringref.hpp>
#include <catch2/catch_test_case_info.hpp> #include <catch2/catch_test_case_info.hpp>
#include <catch2/internal/catch_console_width.hpp> #include <catch2/internal/catch_console_width.hpp>
#include <catch2/internal/catch_stream.hpp>
#include <cfloat> #include <cfloat>
#include <cstdio> #include <cstdio>
@ -152,7 +151,7 @@ private:
if (result.hasExpandedExpression()) { if (result.hasExpandedExpression()) {
stream << "with expansion:\n"; stream << "with expansion:\n";
Colour colourGuard(Colour::ReconstructedExpression); Colour colourGuard(Colour::ReconstructedExpression);
stream << Column(result.getExpandedExpression()).indent(2) << '\n'; stream << TextFlow::Column(result.getExpandedExpression()).indent(2) << '\n';
} }
} }
void printMessage() const { void printMessage() const {
@ -161,7 +160,7 @@ private:
for (auto const& msg : messages) { for (auto const& msg : messages) {
// If this assertion is a warning ignore any INFO messages // If this assertion is a warning ignore any INFO messages
if (printInfoMessages || msg.type != ResultWas::Info) if (printInfoMessages || msg.type != ResultWas::Info)
stream << Column(msg.message).indent(2) << '\n'; stream << TextFlow::Column(msg.message).indent(2) << '\n';
} }
} }
void printSourceInfo() const { void printSourceInfo() const {
@ -297,10 +296,10 @@ public:
m_isOpen = true; m_isOpen = true;
*this << RowBreak(); *this << RowBreak();
Columns headerCols; TextFlow::Columns headerCols;
Spacer spacer(2); auto spacer = TextFlow::Spacer(2);
for (auto const& info : m_columnInfos) { for (auto const& info : m_columnInfos) {
headerCols += Column(info.name).width(static_cast<std::size_t>(info.width - 2)); headerCols += TextFlow::Column(info.name).width(static_cast<std::size_t>(info.width - 2));
headerCols += spacer; headerCols += spacer;
} }
m_os << headerCols << '\n'; m_os << headerCols << '\n';
@ -438,7 +437,7 @@ void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) {
void ConsoleReporter::benchmarkPreparing(std::string const& name) { void ConsoleReporter::benchmarkPreparing(std::string const& name) {
lazyPrintWithoutClosingBenchmarkTable(); lazyPrintWithoutClosingBenchmarkTable();
auto nameCol = Column(name).width(static_cast<std::size_t>(m_tablePrinter->columnInfos()[0].width - 2)); auto nameCol = TextFlow::Column(name).width(static_cast<std::size_t>(m_tablePrinter->columnInfos()[0].width - 2));
bool firstLine = true; bool firstLine = true;
for (auto line : nameCol) { for (auto line : nameCol) {
@ -585,7 +584,7 @@ void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t
i += 2; i += 2;
else else
i = 0; i = 0;
stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n'; stream << TextFlow::Column(_string).indent(indent + i).initialIndent(indent) << '\n';
} }
struct SummaryColumn { struct SummaryColumn {

View File

@ -13,7 +13,7 @@
#include <catch2/catch_tostring.hpp> #include <catch2/catch_tostring.hpp>
#include <catch2/internal/catch_string_manip.hpp> #include <catch2/internal/catch_string_manip.hpp>
#include <catch2/catch_reporter_registrars.hpp> #include <catch2/catch_reporter_registrars.hpp>
#include <catch2/internal/catch_text.hpp> #include <catch2/internal/catch_textflow.hpp>
#include <catch2/interfaces/catch_interfaces_config.hpp> #include <catch2/interfaces/catch_interfaces_config.hpp>
#include <catch2/catch_test_case_info.hpp> #include <catch2/catch_test_case_info.hpp>
@ -266,7 +266,7 @@ namespace Catch {
} }
if (result.hasExpandedExpression()) { if (result.hasExpandedExpression()) {
rss << "with expansion:\n"; rss << "with expansion:\n";
rss << Column(result.getExpandedExpression()).indent(2) << '\n'; rss << TextFlow::Column(result.getExpandedExpression()).indent(2) << '\n';
} }
} else { } else {
rss << '\n'; rss << '\n';

View File

@ -7,7 +7,7 @@
#include <catch2/internal/catch_string_manip.hpp> #include <catch2/internal/catch_string_manip.hpp>
#include <catch2/internal/catch_enforce.hpp> #include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_text.hpp> #include <catch2/internal/catch_textflow.hpp>
#include <catch2/catch_test_case_info.hpp> #include <catch2/catch_test_case_info.hpp>
#include <cassert> #include <cassert>
@ -23,7 +23,7 @@ namespace Catch {
i += 2; i += 2;
else else
i = 0; i = 0;
os << Column(_string) os << TextFlow::Column(_string)
.indent(indent + i) .indent(indent + i)
.initialIndent(indent) << '\n'; .initialIndent(indent) << '\n';
} }