Ookii.CommandLine for C++  1.0.0
line_wrapping_stream.h
Go to the documentation of this file.
1 #pragma once
5 
6 #include <cassert>
7 #include <vector>
8 #include "console_helper.h"
9 
10 namespace ookii
11 {
12 
15  constexpr size_t use_console_width = std::numeric_limits<size_t>::max();
16 
36  template<typename CharType, typename Traits = std::char_traits<CharType>>
37  class basic_line_wrapping_streambuf : public std::basic_streambuf<CharType, Traits>
38  {
39  public:
41  using base_type = std::basic_streambuf<CharType, Traits>;
43  using int_type = typename base_type::int_type;
45  using char_type = typename base_type::char_type;
47  using traits_type = typename base_type::traits_type;
48 
52  basic_line_wrapping_streambuf() noexcept = default;
53 
61  basic_line_wrapping_streambuf(base_type *streambuf, size_t max_line_length)
62  {
63  init(streambuf, max_line_length);
64  }
65 
68  {
69  swap(other);
70  }
71 
74  {
75  swap(other);
76  return *this;
77  }
78 
81 
89  void init(base_type *streambuf, size_t max_line_length) noexcept
90  {
91  if (_base_streambuf != nullptr)
92  flush_buffer();
93 
94  _base_streambuf = streambuf;
95 
96  // Check if the caller wants to use the console width.
97  if (max_line_length == use_console_width)
98  {
99  _max_line_length = get_console_width();
100  if (_max_line_length != 0)
101  {
102  // Subtract one because it looks nicer.
103  --_max_line_length;
104  }
105  }
106  else
107  {
108  _max_line_length = max_line_length;
109  }
110 
111  // Don't allow super long lines, to avoid allocating large buffers.
112  if (_max_line_length > c_max_allowed_line_length)
113  {
114  _max_line_length = c_max_allowed_line_length;
115  }
116 
117  // Allocate a buffer only if line wrapping is used.
118  if (_max_line_length > 0)
119  {
120  // Make the buffer twice the length of the line to simplify the line wrapping code.
121  _buffer.resize(_max_line_length * 2);
122  reset_put_area();
123  }
124  }
125 
127  size_t indent() const
128  {
129  return _indent_count;
130  }
131 
134  void indent(size_t indent) noexcept
135  {
136  if (_max_line_length > 0 && indent >= _max_line_length)
137  _indent_count = 0;
138  else
139  _indent_count = indent;
140  }
141 
149  {
150  if (_base_streambuf == nullptr)
151  {
152  return false;
153  }
154 
155  if (this->pubsync() != 0)
156  {
157  return false;
158  }
159 
160  if (this->pptr() != this->pbase())
161  {
162  if (is_eof(this->sputc(_new_line)))
163  {
164  return false;
165  }
166 
167  if (this->pubsync() != 0)
168  {
169  return false;
170  }
171 
172  assert(this->pptr() == this->pbase());
173  }
174 
175  _need_indent = false;
176  return true;
177  }
178 
181  void swap(basic_line_wrapping_streambuf &other) noexcept
182  {
183  if (this != std::addressof(other))
184  {
185  base_type::swap(other);
186  std::swap(_base_streambuf, other._base_streambuf);
187  std::swap(_max_line_length, other._max_line_length);
188  std::swap(_buffer, other._buffer);
189  std::swap(_new_line, other._new_line);
190  std::swap(_space, other._space);
191  std::swap(_indent_count, other._indent_count);
192  std::swap(_need_indent, other._need_indent);
193  }
194  }
195 
196  protected:
207  virtual int_type overflow(int_type ch = traits_type::eof()) override
208  {
209  if (_base_streambuf == nullptr)
210  {
211  return traits_type::eof();
212  }
213 
214  // If there is no maximum line length set, just forward the characters to the base stream.
215  if (_buffer.size() == 0)
216  {
217  // Nothing to do if this is eof.
218  if (is_eof(ch))
219  {
220  return traits_type::not_eof(ch);
221  }
222 
223  // Write indentation if needed.
224  // N.B. Don't indent blank lines.
225  if (_need_indent && !is_new_line(ch))
226  {
227  if (!write_indent())
228  {
229  return traits_type::eof();
230  }
231  }
232 
233  // If a line break, indicate the next line needs indentation
234  if (is_new_line(ch))
235  {
236  _need_indent = true;
237  }
238 
239  // Write the character to the base stream.
240  return _base_streambuf->sputc(traits_type::to_char_type(ch));
241  }
242 
243  // If the buffer is not currently empty, try to flush it.
244  if (this->pptr() > this->pbase() && !flush_buffer())
245  {
246  return traits_type::eof();
247  }
248 
249  // The buffer must now have space for at least one character.
250  if (!is_eof(ch))
251  {
252  assert(this->pptr() != this->epptr());
253  this->sputc(traits_type::to_char_type(ch));
254  }
255 
256  // Return a character other than eof to indicate success.
257  return traits_type::not_eof(ch);
258  }
259 
262  virtual int sync() override
263  {
264  // Attempt to overflow the buffer, and if that succeeds also flush the underlying stream
265  // buffer.
266  if (_base_streambuf != nullptr && !is_eof(overflow()))
267  {
268  return _base_streambuf->pubsync();
269  }
270 
271  return -1;
272  }
273 
276  virtual void imbue(const std::locale &loc) override
277  {
278  if (_base_streambuf != nullptr)
279  {
280  // Change the locale of the underlying buffer.
281  _base_streambuf->pubimbue(loc);
282  update_locale_characters(loc);
283  }
284  }
285 
286  private:
287  // Write the contents of the buffer to the underlying stream buffer, wrapping lines and adding
288  // indentation as necessary.
289  bool flush_buffer()
290  {
291  if (_base_streambuf == nullptr)
292  {
293  return false;
294  }
295 
296  auto start = this->pbase();
297  auto end = this->pptr();
298 
299  // Only called when there is a buffer.
300  assert(start != nullptr && end != nullptr);
301 
302  std::streamsize count;
303  char_type *potential_line_break{};
304  char_type *new_start{};
305  auto locale = this->getloc();
306  size_t line_length{};
307  if (_need_indent)
308  {
309  line_length = _indent_count;
310  }
311 
312  // Loop over the contents of the buffer.
313  for (auto current = start; current < end; ++current)
314  {
315  // Check if this character can be used as a line break.
316  if (std::isspace(*current, locale))
317  {
318  potential_line_break = current;
319  }
320 
321  // Check if we need to output a line.
322  if (++line_length > _max_line_length || traits_type::eq(*current, _new_line))
323  {
324  // Before writing the line, add indentation if necessary.
325  // N.B. Don't indent empty lines.
326  if (_need_indent && (line_length - _indent_count) > 1)
327  {
328  if (!write_indent())
329  {
330  return false;
331  }
332  }
333 
334  // If no place was found to break the line, break it here.
335  if (potential_line_break == nullptr)
336  {
337  potential_line_break = current;
338  new_start = current;
339  }
340  else
341  {
342  new_start = potential_line_break + 1;
343  }
344 
345  // Write the line up to the break spot.
346  count = potential_line_break - start;
347  if (_base_streambuf->sputn(start, count) < count)
348  {
349  return false;
350  }
351 
352  // Write the line break.
353  _base_streambuf->sputc(_new_line);
354 
355  // Update the state for the new line.
356  start = new_start;
357  potential_line_break = nullptr;
358  line_length = ((current + 1) - start) + _indent_count;
359  _need_indent = true;
360  }
361  }
362 
363  // Move the remaining characters to the front for the next flush.
364  // N.B. line_length can't be used for this because it includes indent character count.
365  count = 0;
366  if (end > start)
367  {
368  count = end - start;
369  traits_type::move(this->pbase(), start, static_cast<size_t>(count));
370  }
371 
372  // Reset the put area, and indicate the number of characters moved to the front.
373  reset_put_area(static_cast<int>(count));
374 
375  return true;
376  }
377 
378  // Reset the put area pointers.
379  void reset_put_area(int valid_data = 0)
380  {
381  this->setp(_buffer.data(), _buffer.data() + _buffer.size());
382  if (valid_data > 0)
383  {
384  this->pbump(valid_data);
385  }
386  }
387 
388  // Update the new line and space characters based on the specified locale.
389  void update_locale_characters(const std::locale &loc)
390  {
391  auto &ctype = std::use_facet<std::ctype<char_type>>(loc);
392  _new_line = ctype.widen('\n');
393  _space = ctype.widen(' ');
394  }
395 
396  // Write indentation to the underlying stream buffer.
397  bool write_indent()
398  {
399  if (_base_streambuf == nullptr)
400  {
401  return false;
402  }
403 
404  assert(_need_indent);
405  for (size_t i = 0; i < _indent_count; ++i)
406  {
407  if (is_eof(_base_streambuf->sputc(_space)))
408  {
409  return false;
410  }
411  }
412 
413  _need_indent = false;
414  return true;
415  }
416 
417  // Checks if the character (in integer representation) equals a new line.
418  bool is_new_line(int_type ch) const
419  {
420  return traits_type::eq_int_type(ch, traits_type::to_int_type(_new_line));
421  }
422 
423  // Checks if the character (in integer representation) is an end-of-file character.
424  static bool is_eof(int_type ch)
425  {
426  return traits_type::eq_int_type(ch, traits_type::eof());
427  }
428 
429  static constexpr size_t c_max_allowed_line_length = 65536;
430 
431  base_type *_base_streambuf{};
432  size_t _max_line_length{};
433  std::vector<char_type> _buffer;
434  char_type _new_line{};
435  char_type _space{};
436  size_t _indent_count{};
437  bool _need_indent{};
438  };
439 
444 
445  namespace details
446  {
447 
448  // Returns a stream's buffer, or NULL if the stream doesn't use a basic_line_wrapping_streambuf.
449  //
450  // N.B. This may require RTTI.
451  template<typename CharType, typename Traits>
452  auto get_line_wrapping_streambuf(std::basic_ostream<CharType, Traits> &stream)
453  {
454  return dynamic_cast<basic_line_wrapping_streambuf<CharType, Traits>*>(stream.rdbuf());
455  }
456 
457  // Stream manipulator to change the indent.
458  struct set_indent_helper
459  {
460  size_t indent;
461  };
462 
463  // Changes the indent size of the stream.
464  //
465  // N.B. Has no effect if the stream does not use a basic_line_wrapping_streambuf.
466  template<typename CharType, typename Traits>
467  std::basic_ostream<CharType, Traits> &operator<<(std::basic_ostream<CharType, Traits> &stream, const set_indent_helper &helper)
468  {
469  auto buffer = get_line_wrapping_streambuf(stream);
470  if (buffer != nullptr)
471  {
472  buffer->indent(helper.indent);
473  }
474 
475  return stream;
476  }
477 
478  }
479 
493  inline details::set_indent_helper set_indent(size_t indent)
494  {
495  return details::set_indent_helper{indent};
496  }
497 
514  template<typename CharType, typename Traits>
515  std::basic_ostream<CharType, Traits> &reset_indent(std::basic_ostream<CharType, Traits> &stream)
516  {
517  auto buffer = details::get_line_wrapping_streambuf(stream);
518  if (buffer != nullptr)
519  {
520  if (!buffer->reset_indent())
521  {
522  stream.setstate(std::ios::failbit);
523  }
524  }
525 
526  return stream;
527  }
528 
548  template<typename CharType, typename Traits = std::char_traits<CharType>>
549  class basic_line_wrapping_ostream : public std::basic_ostream<CharType, Traits>
550  {
551  public:
553  using base_type = std::basic_ostream<CharType, Traits>;
555  using streambuf_type = std::basic_streambuf<CharType, Traits>;
556 
564  basic_line_wrapping_ostream(base_type &base_stream, size_t max_line_length)
565  : base_type{std::addressof(_buffer)}
566  {
567  _buffer.init(base_stream.rdbuf(), max_line_length);
568  this->imbue(base_stream.rdbuf()->getloc());
569  }
570 
574  : base_type{std::addressof(_buffer)}
575  {
576  swap(other);
577  }
578 
582  {
583  swap(other);
584  return *this;
585  }
586 
592  static basic_line_wrapping_ostream for_cout(short default_width = 80)
593  {
594  return {console_stream<CharType>::cout(), static_cast<size_t>(get_console_width(default_width))};
595  }
596 
602  static basic_line_wrapping_ostream for_cerr(short default_width = 80)
603  {
604  return {console_stream<CharType>::cerr(), static_cast<size_t>(get_console_width(default_width))};
605  }
606 
610  void swap(basic_line_wrapping_ostream &other) noexcept
611  {
612  if (this != std::addressof(other))
613  {
614  base_type::swap(other);
615  _buffer.swap(other._buffer);
616  }
617  }
618 
619  private:
621  };
622 
627 
628 }
Output stream that wraps lines on white-space characters at the specified line length,...
Definition: line_wrapping_stream.h:550
void swap(basic_line_wrapping_ostream &other) noexcept
Swaps this basic_line_wrapping_ostream instance with another.
Definition: line_wrapping_stream.h:610
static basic_line_wrapping_ostream for_cout(short default_width=80)
Creates a basic_line_wrapping_ostream that writes to the standard output stream, using the console wi...
Definition: line_wrapping_stream.h:592
static basic_line_wrapping_ostream for_cerr(short default_width=80)
Creates a basic_line_wrapping_ostream that writes to the standard error stream, using the console wid...
Definition: line_wrapping_stream.h:602
std::basic_streambuf< CharType, Traits > streambuf_type
The concrete base stream buffer type used by this stream.
Definition: line_wrapping_stream.h:555
basic_line_wrapping_ostream(basic_line_wrapping_ostream &&other) noexcept
Move constructor.
Definition: line_wrapping_stream.h:573
std::basic_ostream< CharType, Traits > base_type
The concrete type that this class derives from.
Definition: line_wrapping_stream.h:553
basic_line_wrapping_ostream(base_type &base_stream, size_t max_line_length)
Initializes a new instance of the basic_line_wrapping_ostream class with the specified underlying str...
Definition: line_wrapping_stream.h:564
basic_line_wrapping_ostream & operator=(basic_line_wrapping_ostream &&other) noexcept
Move assignment operator.
Definition: line_wrapping_stream.h:581
Stream buffer that wraps lines on white-space characters at the specified line length,...
Definition: line_wrapping_stream.h:38
size_t indent() const
Gets the current number of spaces that each line is indented with.
Definition: line_wrapping_stream.h:127
void swap(basic_line_wrapping_streambuf &other) noexcept
Swaps the contents basic_line_wrapping_streambuf instance with another.
Definition: line_wrapping_stream.h:181
void indent(size_t indent) noexcept
Sets the number of spaces that each line is indented with.
Definition: line_wrapping_stream.h:134
basic_line_wrapping_streambuf() noexcept=default
Initializes a new instance of the basic_line_wrapping_streambuf class.
typename base_type::int_type int_type
Integer type used by the base type.
Definition: line_wrapping_stream.h:43
basic_line_wrapping_streambuf & operator=(basic_line_wrapping_streambuf &&other) noexcept
Move assignment operator.
Definition: line_wrapping_stream.h:73
void init(base_type *streambuf, size_t max_line_length) noexcept
Initializes this basic_line_wrapping_streambuf instance with the specified underlying stream buffer a...
Definition: line_wrapping_stream.h:89
virtual int sync() override
Flushes the buffer to the underlying stream buffer.
Definition: line_wrapping_stream.h:262
bool reset_indent()
Disables indentation for the next line.
Definition: line_wrapping_stream.h:148
virtual int_type overflow(int_type ch=traits_type::eof()) override
Ensure there is space to write at least one character to the buffer.
Definition: line_wrapping_stream.h:207
typename base_type::char_type char_type
Character type used by the base type.
Definition: line_wrapping_stream.h:45
virtual void imbue(const std::locale &loc) override
Change the locale of the stream buffer.
Definition: line_wrapping_stream.h:276
std::basic_streambuf< CharType, Traits > base_type
The concrete type that this class derives from.
Definition: line_wrapping_stream.h:41
basic_line_wrapping_streambuf(basic_line_wrapping_streambuf &&other) noexcept
Move constructor.
Definition: line_wrapping_stream.h:67
typename base_type::traits_type traits_type
Traits type used by the base type.
Definition: line_wrapping_stream.h:47
Provides helpers for using the console.
Namespace containing the core Ookii.CommandLine.Cpp types.
Definition: command_line_argument.h:16
std::basic_ostream< CharType, Traits > & reset_indent(std::basic_ostream< CharType, Traits > &stream)
IO manipulator that lets the next line start at the beginning of the line, without indenting it.
Definition: line_wrapping_stream.h:515
constexpr size_t use_console_width
Indicates that the basic_line_wrapping_streambuf should use the console width as the line length.
Definition: line_wrapping_stream.h:15
void swap(owned_or_borrowed_ptr< T > &left, owned_or_borrowed_ptr< T > &right) noexcept
Swaps two owned_or_borrowed_ptr instances.
Definition: owned_or_borrowed_ptr.h:219
details::set_indent_helper set_indent(size_t indent)
IO manipulator that changes the number of spaces that each line is indented with for a line wrapping ...
Definition: line_wrapping_stream.h:493
short get_console_width(short default_width=80) noexcept
Determines the width of the console.
Definition: console_helper.h:62
Template to determine the correct console streams based on the character type.
Definition: console_helper.h:95