cgal/Stream_support/include/CGAL/IO/Indenting_ostream.h

347 lines
12 KiB
C++

// Copyright (c) 2025
// Utrecht University (The Netherlands),
// ETH Zurich (Switzerland),
// INRIA Sophia-Antipolis (France),
// Max-Planck-Institute Saarbruecken (Germany),
// and Tel-Aviv University (Israel). All rights reserved.
//
// This file is part of CGAL (www.cgal.org)
//
// $URL$
// $Id$
// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s) : Laurent Rineau
//
// Documentation partially generated by Github Copilot
#ifndef CGAL_IO_INDENTING_OSTREAM_H
#define CGAL_IO_INDENTING_OSTREAM_H
#include <CGAL/config.h>
#include <ios>
#include <streambuf>
#include <string>
#include <tuple>
namespace CGAL {
namespace IO {
/**
* \ingroup PkgStreamSupportRef
*
* The class template `Basic_indenting_streambuf` wraps another `basic_streambuf`
* and automatically adds indentation at the beginning of each line. This is
* useful for formatting debug output with consistent indentation levels.
*
* \tparam CharT Character type (typically `char` or `wchar_t`)
* \tparam Traits Character traits type
*
* \sa Basic_indenting_stream_guard
* \sa make_indenting_guards
*/
template <typename CharT, typename Traits = std::char_traits<CharT>>
class Basic_indenting_streambuf : public std::basic_streambuf<CharT, Traits>, protected Traits
{
public:
using char_type = CharT; ///< Character type
using traits_type = Traits; ///< Character traits type
using int_type = typename traits_type::int_type; ///< Integer type for character representation
using pos_type = typename traits_type::pos_type; ///< Position type for stream positioning
using off_type = typename traits_type::off_type; ///< Offset type for stream positioning
using streambuf_type = std::basic_streambuf<char_type, traits_type>; ///< Type of the wrapped streambuf
using string = std::basic_string<char_type>; ///< String type matching character type
private:
streambuf_type* wrapped_buf_;
string indent_string_;
bool at_line_start_;
public:
/**
* \brief constructs an indenting streambuf wrapper.
*
* \param wrapped_buf The underlying streambuf to wrap
* \param indent_string The string to use for indentation (default: 2 spaces)
*/
explicit
Basic_indenting_streambuf(streambuf_type& wrapped_buf,
const string& indent_string = {2, char_type(' ')})
: wrapped_buf_(&wrapped_buf)
, indent_string_(indent_string)
, at_line_start_(true)
{
}
/** \brief gets the current indentation string. */
const string& indent_string() const { return indent_string_; }
/**
* \brief sets a new indentation string.
*
* \param new_indent The new indentation string
*/
void set_indent_string(const string& new_indent) { indent_string_ = new_indent; }
/**
* \brief sets indentation level using repeated spaces.
*
* \param level Number of indentation levels
* \param spaces_per_level Number of spaces per level (default: 2)
*/
void set_indent_level(int level, int spaces_per_level = 2) {
indent_string_ = string(level * spaces_per_level, char_type(' '));
}
/** \brief gets the wrapped streambuf. */
streambuf_type& wrapped_streambuf() const { return *wrapped_buf_; }
protected:
using traits_type::eof;
using traits_type::not_eof;
using traits_type::to_char_type;
using traits_type::to_int_type;
using traits_type::eq_int_type;
int_type overflow(int_type ch = eof()) override {
if(eq_int_type(ch, eof())) {
return wrapped_buf_->pubsync() == 0 ? not_eof(ch) : eof();
}
// If we're at the start of a line, output the indentation first
if(at_line_start_ && !indent_string_.empty()) {
for(char_type indent_char : indent_string_) {
if(eq_int_type(wrapped_buf_->sputc(indent_char), eof())) {
return eof();
}
}
at_line_start_ = false;
}
// Output the actual character
int_type result = wrapped_buf_->sputc(to_char_type(ch));
// Check if this character is a newline
if(!eq_int_type(result, eof()) && to_char_type(ch) == char_type('\n')) {
at_line_start_ = true;
}
return result;
}
int sync() override { return wrapped_buf_->pubsync(); }
std::streamsize xsputn(const char_type* s, std::streamsize count) override {
std::streamsize written = 0;
for(std::streamsize i = 0; i < count; ++i) {
if(eq_int_type(overflow(to_int_type(s[i])), eof())) {
break;
}
++written;
}
return written;
}
pos_type seekoff(off_type off,
std::ios_base::seekdir dir,
std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) override {
return wrapped_buf_->pubseekoff(off, dir, which);
}
pos_type seekpos(pos_type pos, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) override {
return wrapped_buf_->pubseekpos(pos, which);
}
std::streamsize showmanyc() override {
return wrapped_buf_->in_avail();
}
std::streamsize xsgetn(char_type* s, std::streamsize count) override {
return wrapped_buf_->sgetn(s, count);
}
int_type underflow() override {
return wrapped_buf_->sgetc();
}
int_type uflow() override {
return wrapped_buf_->sbumpc();
}
int_type pbackfail(int_type c = eof()) override {
return wrapped_buf_->sputbackc(to_char_type(c));
}
void imbue(const std::locale& loc) override {
wrapped_buf_->pubimbue(loc);
}
streambuf_type* setbuf(char_type* s, std::streamsize n) override {
return wrapped_buf_->pubsetbuf(s, n);
}
};
/// \ingroup PkgStreamSupportRef
/// Type alias for `Basic_indenting_streambuf<char>`
using Indenting_streambuf = Basic_indenting_streambuf<char>;
/// \ingroup PkgStreamSupportRef
/// Type alias for `Basic_indenting_streambuf<wchar_t>`
using Indenting_wstreambuf = Basic_indenting_streambuf<wchar_t>;
/**
* \ingroup PkgStreamSupportRef
*
* RAII helper class for temporarily installing an indenting streambuf on a stream.
*
* This class automatically restores the original streambuf when it goes out of scope.
* It provides a convenient way to add indentation to output streams within a specific scope.
*
* \tparam StreamT The stream type (e.g., `std::ostream`, `std::wostream`)
*
* \sa Basic_indenting_streambuf
* \sa make_indenting_guards
*/
template <typename StreamT>
class Basic_indenting_stream_guard
{
public:
using char_type = typename StreamT::char_type; ///< Character type
using traits_type = typename StreamT::traits_type; ///< Character traits type
using streambuf_type = Basic_indenting_streambuf<char_type, traits_type>; ///< Type of the indenting streambuf
using wrapped_streambuf_type = std::basic_streambuf<char_type, traits_type>; ///< Type of the wrapped streambuf
using string = std::basic_string<char_type>; ///< String type matching character type
private:
StreamT& stream_;
wrapped_streambuf_type* original_buf_;
string original_indent_string_;
streambuf_type indenting_buf_;
public:
/**
* \brief constructs and installs an indenting streambuf on the given stream.
*
* \param stream The stream to modify
* \param indent_string The indentation string to use
*/
explicit Basic_indenting_stream_guard(StreamT& stream,
const string& indent_string)
: stream_(stream)
, original_buf_(stream.rdbuf())
, indenting_buf_(*original_buf_, indent_string)
{
if(auto old_buf = dynamic_cast<streambuf_type*>(original_buf_)) {
original_indent_string_ = old_buf->indent_string();
old_buf->set_indent_string(original_indent_string_ + indent_string);
} else {
stream.rdbuf(&indenting_buf_);
}
}
/**
* \brief constructs and installs an indenting streambuf on the given stream
*
* \param stream The stream to modify
* \param spaces_per_level Number of indentation spaces
*/
Basic_indenting_stream_guard(StreamT& stream, int spaces_per_level = 2)
: Basic_indenting_stream_guard(stream,
string(spaces_per_level, char_type(' '))) {
}
// Non-copyable, movable
Basic_indenting_stream_guard(const Basic_indenting_stream_guard&) = delete;
Basic_indenting_stream_guard& operator=(const Basic_indenting_stream_guard&) = delete;
Basic_indenting_stream_guard(Basic_indenting_stream_guard&& other)
: stream_(other.stream_)
, original_buf_(other.original_buf_)
, original_indent_string_(std::move(other.original_indent_string_))
, indenting_buf_(std::move(other.indenting_buf_))
{
// Update the stream to point to the new buffer location if it was using the old one
if(stream_.rdbuf() == &other.indenting_buf_) {
stream_.rdbuf(&indenting_buf_);
}
// Mark other as moved-from so it won't restore the stream
other.original_buf_ = nullptr;
}
Basic_indenting_stream_guard& operator=(Basic_indenting_stream_guard&&) = delete;
/** \brief Destructor - restores the original streambuf. */
~Basic_indenting_stream_guard() {
if(!original_buf_) return; // Moved-from object
if(auto old_buf = dynamic_cast<streambuf_type*>(original_buf_)) {
old_buf->set_indent_string(original_indent_string_);
} else {
stream_.rdbuf(original_buf_);
}
}
};
/// \ingroup PkgStreamSupportRef
/// Type alias for `basic_indenting_stream_guard<std::ostream>`
using Indenting_stream_guard = Basic_indenting_stream_guard<std::ostream>;
/// \ingroup PkgStreamSupportRef
/// Type alias for `basic_indenting_stream_guard<std::wostream>`
using Indenting_wstream_guard = Basic_indenting_stream_guard<std::wostream>;
/**
* \ingroup PkgStreamSupportRef
*
* \brief creates indenting guards for multiple streams simultaneously.
*
* This helper function creates `Basic_indenting_stream_guard` objects for
* multiple streams at once. All streams will use the same indentation settings,
* and their original streambufs will be automatically restored when the guards
* go out of scope. The guards are returned in a tuple.
*
* \tparam Streams The stream types (deduced from arguments)
* \param spaces_per_level Number of spaces per indentation level
* \param streams The streams to apply indentation to
* \return A tuple of Basic_indenting_stream_guard objects
*
* \sa Basic_indenting_stream_guard
*
* Example usage:
* \code
* auto guards = CGAL::IO::make_indenting_guards(2, std::cout, std::cerr);
* std::cout << "Indented output\n";
* std::cerr << "Indented error\n";
* \endcode
*/
template <typename... Streams>
auto make_indenting_guards(int spaces_per_level, Streams&... streams) {
return std::make_tuple(Basic_indenting_stream_guard<Streams>(streams, spaces_per_level)...);
}
/**
* \ingroup PkgStreamSupportRef
*
* \brief creates indenting guards for multiple streams with a custom indent string.
*
* This overload allows specifying a custom indentation string instead of a number
* of spaces.
*
* \tparam Streams The stream types (deduced from arguments)
* \param indent_string The indentation string to use
* \param streams The streams to apply indentation to
* \return A tuple of Basic_indenting_stream_guard objects
*
* \sa Basic_indenting_stream_guard
*/
template <typename... Streams>
auto make_indenting_guards(const std::string& indent_string, Streams&... streams) {
return std::make_tuple(Basic_indenting_stream_guard<Streams>(streams, indent_string)...);
}
} // namespace IO
} // namespace CGAL
#endif // CGAL_IO_INDENTING_OSTREAM_H