/ externals / catch / src / catch2 / internal / catch_textflow.cpp
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