catch_textflow.cpp
1 2 // Copyright Catch2 Authors 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE.txt or copy at 5 // https://www.boost.org/LICENSE_1_0.txt) 6 7 // SPDX-License-Identifier: BSL-1.0 8 #include <catch2/internal/catch_textflow.hpp> 9 10 #include <algorithm> 11 #include <cstring> 12 #include <ostream> 13 14 namespace { 15 bool isWhitespace( char c ) { 16 return c == ' ' || c == '\t' || c == '\n' || c == '\r'; 17 } 18 19 bool isBreakableBefore( char c ) { 20 static const char chars[] = "[({<|"; 21 return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr; 22 } 23 24 bool isBreakableAfter( char c ) { 25 static const char chars[] = "])}>.,:;*+-=&/\\"; 26 return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr; 27 } 28 29 bool isBoundary( std::string const& line, size_t at ) { 30 assert( at > 0 ); 31 assert( at <= line.size() ); 32 33 return at == line.size() || 34 ( isWhitespace( line[at] ) && !isWhitespace( line[at - 1] ) ) || 35 isBreakableBefore( line[at] ) || 36 isBreakableAfter( line[at - 1] ); 37 } 38 39 } // namespace 40 41 namespace Catch { 42 namespace TextFlow { 43 44 void Column::const_iterator::calcLength() { 45 m_addHyphen = false; 46 m_parsedTo = m_lineStart; 47 48 std::string const& current_line = m_column.m_string; 49 if ( current_line[m_lineStart] == '\n' ) { 50 ++m_parsedTo; 51 } 52 53 const auto maxLineLength = m_column.m_width - indentSize(); 54 const auto maxParseTo = std::min(current_line.size(), m_lineStart + maxLineLength); 55 while ( m_parsedTo < maxParseTo && 56 current_line[m_parsedTo] != '\n' ) { 57 ++m_parsedTo; 58 } 59 60 // If we encountered a newline before the column is filled, 61 // then we linebreak at the newline and consider this line 62 // finished. 63 if ( m_parsedTo < m_lineStart + maxLineLength ) { 64 m_lineLength = m_parsedTo - m_lineStart; 65 } else { 66 // Look for a natural linebreak boundary in the column 67 // (We look from the end, so that the first found boundary is 68 // the right one) 69 size_t newLineLength = maxLineLength; 70 while ( newLineLength > 0 && !isBoundary( current_line, m_lineStart + newLineLength ) ) { 71 --newLineLength; 72 } 73 while ( newLineLength > 0 && 74 isWhitespace( current_line[m_lineStart + newLineLength - 1] ) ) { 75 --newLineLength; 76 } 77 78 // If we found one, then that is where we linebreak 79 if ( newLineLength > 0 ) { 80 m_lineLength = newLineLength; 81 } else { 82 // Otherwise we have to split text with a hyphen 83 m_addHyphen = true; 84 m_lineLength = maxLineLength - 1; 85 } 86 } 87 } 88 89 size_t Column::const_iterator::indentSize() const { 90 auto initial = 91 m_lineStart == 0 ? m_column.m_initialIndent : std::string::npos; 92 return initial == std::string::npos ? m_column.m_indent : initial; 93 } 94 95 std::string 96 Column::const_iterator::addIndentAndSuffix( size_t position, 97 size_t length ) const { 98 std::string ret; 99 const auto desired_indent = indentSize(); 100 ret.reserve( desired_indent + length + m_addHyphen ); 101 ret.append( desired_indent, ' ' ); 102 ret.append( m_column.m_string, position, length ); 103 if ( m_addHyphen ) { 104 ret.push_back( '-' ); 105 } 106 107 return ret; 108 } 109 110 Column::const_iterator::const_iterator( Column const& column ): m_column( column ) { 111 assert( m_column.m_width > m_column.m_indent ); 112 assert( m_column.m_initialIndent == std::string::npos || 113 m_column.m_width > m_column.m_initialIndent ); 114 calcLength(); 115 if ( m_lineLength == 0 ) { 116 m_lineStart = m_column.m_string.size(); 117 } 118 } 119 120 std::string Column::const_iterator::operator*() const { 121 assert( m_lineStart <= m_parsedTo ); 122 return addIndentAndSuffix( m_lineStart, m_lineLength ); 123 } 124 125 Column::const_iterator& Column::const_iterator::operator++() { 126 m_lineStart += m_lineLength; 127 std::string const& current_line = m_column.m_string; 128 if ( m_lineStart < current_line.size() && current_line[m_lineStart] == '\n' ) { 129 m_lineStart += 1; 130 } else { 131 while ( m_lineStart < current_line.size() && 132 isWhitespace( current_line[m_lineStart] ) ) { 133 ++m_lineStart; 134 } 135 } 136 137 if ( m_lineStart != current_line.size() ) { 138 calcLength(); 139 } 140 return *this; 141 } 142 143 Column::const_iterator Column::const_iterator::operator++( int ) { 144 const_iterator prev( *this ); 145 operator++(); 146 return prev; 147 } 148 149 std::ostream& operator<<( std::ostream& os, Column const& col ) { 150 bool first = true; 151 for ( auto line : col ) { 152 if ( first ) { 153 first = false; 154 } else { 155 os << '\n'; 156 } 157 os << line; 158 } 159 return os; 160 } 161 162 Column Spacer( size_t spaceWidth ) { 163 Column ret{ "" }; 164 ret.width( spaceWidth ); 165 return ret; 166 } 167 168 Columns::iterator::iterator( Columns const& columns, EndTag ): 169 m_columns( columns.m_columns ), m_activeIterators( 0 ) { 170 171 m_iterators.reserve( m_columns.size() ); 172 for ( auto const& col : m_columns ) { 173 m_iterators.push_back( col.end() ); 174 } 175 } 176 177 Columns::iterator::iterator( Columns const& columns ): 178 m_columns( columns.m_columns ), 179 m_activeIterators( m_columns.size() ) { 180 181 m_iterators.reserve( m_columns.size() ); 182 for ( auto const& col : m_columns ) { 183 m_iterators.push_back( col.begin() ); 184 } 185 } 186 187 std::string Columns::iterator::operator*() const { 188 std::string row, padding; 189 190 for ( size_t i = 0; i < m_columns.size(); ++i ) { 191 const auto width = m_columns[i].width(); 192 if ( m_iterators[i] != m_columns[i].end() ) { 193 std::string col = *m_iterators[i]; 194 row += padding; 195 row += col; 196 197 padding.clear(); 198 if ( col.size() < width ) { 199 padding.append( width - col.size(), ' ' ); 200 } 201 } else { 202 padding.append( width, ' ' ); 203 } 204 } 205 return row; 206 } 207 208 Columns::iterator& Columns::iterator::operator++() { 209 for ( size_t i = 0; i < m_columns.size(); ++i ) { 210 if ( m_iterators[i] != m_columns[i].end() ) { 211 ++m_iterators[i]; 212 } 213 } 214 return *this; 215 } 216 217 Columns::iterator Columns::iterator::operator++( int ) { 218 iterator prev( *this ); 219 operator++(); 220 return prev; 221 } 222 223 std::ostream& operator<<( std::ostream& os, Columns const& cols ) { 224 bool first = true; 225 for ( auto line : cols ) { 226 if ( first ) { 227 first = false; 228 } else { 229 os << '\n'; 230 } 231 os << line; 232 } 233 return os; 234 } 235 236 Columns Column::operator+( Column const& other ) { 237 Columns cols; 238 cols += *this; 239 cols += other; 240 return cols; 241 } 242 243 Columns& Columns::operator+=( Column const& col ) { 244 m_columns.push_back( col ); 245 return *this; 246 } 247 248 Columns Columns::operator+( Column const& col ) { 249 Columns combined = *this; 250 combined += col; 251 return combined; 252 } 253 254 } // namespace TextFlow 255 } // namespace Catch