Stream_support: add indenting and coloring stream wrappers

This commit is contained in:
Laurent Rineau 2025-10-21 16:17:25 +02:00
parent b3e2f204a4
commit eaea32ee9f
5 changed files with 1559 additions and 0 deletions

View File

@ -18,3 +18,7 @@ create_single_source_cgal_program( "off2wav.cpp" )
create_single_source_cgal_program( "off_bbox.cpp" )
create_single_source_cgal_program( "off_glue.cpp" )
create_single_source_cgal_program( "off_transform.cpp" )
create_single_source_cgal_program( "indenting_ostream.cpp" )
create_single_source_cgal_program( "color_ostream.cpp" )

View File

@ -0,0 +1,365 @@
#include <CGAL/IO/Color_ostream.h>
#include <iostream>
/*!
* \example Stream_support/color_ostream.cpp
*
* This example demonstrates how to use CGAL::IO::Basic_color_streambuf
* and CGAL::IO::Basic_color_stream_guard for formatting output with
* ANSI colors.
*/
using CGAL::IO::Ansi_color;
void demonstrate_basic_colors() {
std::cout << "=== Basic Color Usage ===\n\n";
// Example 1: Single color
{
std::cout << "1. Single color (Red):\n";
CGAL::IO::Color_stream_guard guard(std::cout, Ansi_color::Red);
std::cout << "This text is red\n";
std::cout << "All lines in this scope are red\n";
}
std::cout << "Back to normal color\n\n";
// Example 2: Different colors
{
std::cout << "2. Different colors:\n";
{
CGAL::IO::Color_stream_guard green(std::cout, Ansi_color::Green);
std::cout << "Green text\n";
}
{
CGAL::IO::Color_stream_guard blue(std::cout, Ansi_color::Blue);
std::cout << "Blue text\n";
}
{
CGAL::IO::Color_stream_guard yellow(std::cout, Ansi_color::Yellow);
std::cout << "Yellow text\n";
}
}
std::cout << "Back to normal\n\n";
// Example 3: Bright colors
{
std::cout << "3. Bright colors:\n";
{
CGAL::IO::Color_stream_guard bright_red(std::cout, Ansi_color::BrightRed);
std::cout << "Bright red text\n";
}
{
CGAL::IO::Color_stream_guard bright_green(std::cout, Ansi_color::BrightGreen);
std::cout << "Bright green text\n";
}
{
CGAL::IO::Color_stream_guard bright_cyan(std::cout, Ansi_color::BrightCyan);
std::cout << "Bright cyan text\n";
}
}
std::cout << "Back to normal\n\n";
}
void demonstrate_combined_colors() {
std::cout << "=== Combined Colors (Bold, Underline, etc.) ===\n\n";
// Example 1: Bold text
{
std::cout << "1. Bold colors:\n";
CGAL::IO::Color_stream_guard guard(std::cout,
std::vector<Ansi_color>{Ansi_color::Bold, Ansi_color::Red});
std::cout << "This text is bold and red\n";
}
std::cout << "Back to normal\n\n";
// Example 2: Underlined text
{
std::cout << "2. Underlined colors:\n";
CGAL::IO::Color_stream_guard guard(std::cout,
std::vector<Ansi_color>{Ansi_color::Underline, Ansi_color::Blue});
std::cout << "This text is underlined and blue\n";
}
std::cout << "Back to normal\n\n";
// Example 3: Bold + underline + color
{
std::cout << "3. Multiple attributes:\n";
CGAL::IO::Color_stream_guard guard(std::cout,
std::vector<Ansi_color>{Ansi_color::Bold, Ansi_color::Underline, Ansi_color::Green});
std::cout << "This text is bold, underlined, and green\n";
}
std::cout << "Back to normal\n\n";
}
void demonstrate_background_colors() {
std::cout << "=== Background Colors ===\n\n";
{
std::cout << "1. Red background:\n";
CGAL::IO::Color_stream_guard guard(std::cout, Ansi_color::BgRed);
std::cout << "Text with red background\n";
}
std::cout << "Back to normal\n\n";
{
std::cout << "2. Foreground + background:\n";
CGAL::IO::Color_stream_guard guard(std::cout,
std::vector<Ansi_color>{Ansi_color::Yellow, Ansi_color::BgBlue});
std::cout << "Yellow text on blue background\n";
}
std::cout << "Back to normal\n\n";
{
std::cout << "3. Bold white on bright green background:\n";
CGAL::IO::Color_stream_guard guard(std::cout,
std::vector<Ansi_color>{Ansi_color::Bold, Ansi_color::White, Ansi_color::BgBrightGreen});
std::cout << "Bold white on bright green\n";
}
std::cout << "Back to normal\n\n";
}
void demonstrate_nested_colors() {
std::cout << "=== Nested Color Scopes ===\n\n";
{
CGAL::IO::Color_stream_guard outer(std::cout, Ansi_color::Blue);
std::cout << "Outer scope: blue text\n";
{
CGAL::IO::Color_stream_guard inner(std::cout, Ansi_color::Red);
std::cout << "Inner scope: red text\n";
{
CGAL::IO::Color_stream_guard innermost(std::cout, Ansi_color::Green);
std::cout << "Innermost scope: green text\n";
}
std::cout << "Back to inner scope: red text\n";
}
std::cout << "Back to outer scope: blue text\n";
}
std::cout << "Back to normal\n\n";
}
void simulate_colored_debug_output() {
std::cout << "=== Simulated Colored Debug Output ===\n\n";
std::cout << "Algorithm: Starting computation\n";
{
CGAL::IO::Color_stream_guard info(std::cout, Ansi_color::Cyan);
std::cout << "INFO: Initializing data structures\n";
}
{
CGAL::IO::Color_stream_guard success(std::cout,
std::vector<Ansi_color>{Ansi_color::Bold, Ansi_color::Green});
std::cout << "SUCCESS: Data loaded successfully\n";
}
{
CGAL::IO::Color_stream_guard warning(std::cout,
std::vector<Ansi_color>{Ansi_color::Bold, Ansi_color::Yellow});
std::cout << "WARNING: Large dataset detected, may take longer\n";
}
{
CGAL::IO::Color_stream_guard processing(std::cout, Ansi_color::Blue);
std::cout << "Processing vertices...\n";
std::cout << "Processing edges...\n";
std::cout << "Processing faces...\n";
}
{
CGAL::IO::Color_stream_guard success(std::cout,
std::vector<Ansi_color>{Ansi_color::Bold, Ansi_color::Green});
std::cout << "SUCCESS: Computation completed\n";
}
std::cout << "Algorithm finished\n\n";
}
void demonstrate_multiple_streams() {
std::cout << "=== Multiple Streams with make_color_guards ===\n\n";
std::cout << "Before coloring:\n";
std::cout << " cout: Normal output\n";
std::cerr << " cerr: Normal output\n";
// Apply colors to both streams simultaneously
{
std::cout << "\nWith make_color_guards (Red):\n";
auto guards = CGAL::IO::make_color_guards(Ansi_color::Red, std::cout, std::cerr);
std::cout << "Red text on cout\n";
std::cerr << "Red text on cerr\n";
}
std::cout << "\nAfter color scope:\n";
std::cout << " cout: Normal output again\n";
std::cerr << " cerr: Normal output again\n";
// Using combined colors with multiple streams
{
std::cout << "\nBold green on multiple streams:\n";
auto guards = CGAL::IO::make_color_guards(
std::vector<Ansi_color>{Ansi_color::Bold, Ansi_color::Green},
std::cout, std::clog);
std::cout << "cout: Bold green output\n";
std::clog << "clog: Bold green log message\n";
}
std::cout << "\n";
}
void test_color_detection() {
std::cout << "=== Color Detection Tests ===\n\n";
// Test automatic detection in streambuf
std::cout << "1. Testing automatic color detection in Basic_color_streambuf:\n";
{
CGAL::IO::Color_streambuf color_buf(*std::cout.rdbuf(), Ansi_color::Green);
std::cout << " Colors automatically detected as: " << (color_buf.colors_enabled() ? "ENABLED" : "DISABLED")
<< "\n";
// Temporarily install the buffer to show it works
auto* old_buf = std::cout.rdbuf(&color_buf);
std::cout << " (This text should be green only if colors were auto-detected)\n";
std::cout.rdbuf(old_buf);
}
std::cout << "\n";
// Test stdout color support function
std::cout << "2. Testing stdout_supports_color() function:\n";
if(CGAL::IO::stdout_supports_color()) {
std::cout << " stdout supports colors: YES\n";
{
CGAL::IO::Color_stream_guard guard(std::cout, Ansi_color::Green);
std::cout << " (This text should be green if color is truly supported)\n";
}
} else {
std::cout << " stdout supports colors: NO\n";
std::cout << " (Colors are disabled - may be due to NO_COLOR env var,\n";
std::cout << " redirection to file, or unsupported terminal)\n";
}
std::cout << "\n";
// Test stderr color support
std::cout << "3. Testing stderr_supports_color():\n";
if(CGAL::IO::stderr_supports_color()) {
std::cout << " stderr supports colors: YES\n";
{
CGAL::IO::Color_stream_guard guard(std::cerr, Ansi_color::Yellow);
std::cerr << " (This text should be yellow if color is truly supported)\n";
}
} else {
std::cout << " stderr supports colors: NO\n";
}
std::cout << "\n";
// Test stream_supports_color() with different streams
std::cout << "4. Testing stream_supports_color() for different streams:\n";
std::cout << " std::cout:" << (CGAL::IO::stream_supports_color(std::cout) ? "YES" : "NO") << "\n";
std::cout << " std::cerr:" << (CGAL::IO::stream_supports_color(std::cerr) ? "YES" : "NO") << "\n";
std::cout << " std::clog: " << (CGAL::IO::stream_supports_color(std::clog) ? "YES" : "NO") << "\n";
std::cout << "\n";
// Demonstrate automatic coloring (no manual check needed!)
std::cout << "5. Automatic coloring example (no manual checking!):\n";
{
CGAL::IO::Color_stream_guard guard(std::cout,
std::vector<Ansi_color>{Ansi_color::Bold, Ansi_color::Cyan});
std::cout << " This message is automatically colored only if terminal supports it!\n";
std::cout << " No need to check stream_supports_color() manually - it's automatic!\n";
}
std::cout << "\n";
// Demonstrate smart error/warning coloring
std::cout << "6. Smart colored output (automatically adapts to terminal capabilities):\n";
// Info message - colors applied automatically!
std::cout << " ";
{
CGAL::IO::Color_stream_guard guard(std::cout, Ansi_color::Cyan);
std::cout << "INFO:";
}
std::cout << " Normal information message\n";
// Success message - colors applied automatically!
std::cout << " ";
{
CGAL::IO::Color_stream_guard guard(std::cout,
std::vector<Ansi_color>{Ansi_color::Bold, Ansi_color::Green});
std::cout << "SUCCESS:";
}
std::cout << " Operation completed successfully\n";
// Warning message - colors applied automatically!
std::cout << " ";
{
CGAL::IO::Color_stream_guard guard(std::cout,
std::vector<Ansi_color>{Ansi_color::Bold, Ansi_color::Yellow});
std::cout << "WARNING:";
}
std::cout << " Potential issue detected\n";
// Error message (using cerr) - colors applied automatically!
std::cerr << " ";
{
CGAL::IO::Color_stream_guard guard(std::cerr,
std::vector<Ansi_color>{Ansi_color::Bold, Ansi_color::Red});
std::cerr << "ERROR:";
}
std::cerr << " Critical error occurred\n";
std::cout << "\n";
// Environment hints
std::cout << "7. Environment information:\n";
const char* term = std::getenv("TERM");
const char* no_color = std::getenv("NO_COLOR");
std::cout << " TERM variable: " << (term ? term : "(not set)") << "\n";
std::cout << " NO_COLOR variable: " << (no_color ? "SET (colors disabled)" : "(not set)") << "\n";
#ifdef _WIN32
std::cout << " Platform: Windows\n";
const char* ansicon = std::getenv("ANSICON");
std::cout << " ANSICON variable: " << (ansicon ? ansicon : "(not set)") << "\n";
#else
std::cout << " Platform: POSIX (Linux/macOS/Unix)\n";
#endif
std::cout << "\n";
std::cout << " Tip: To disable colors, set the NO_COLOR environment variable:\n";
std::cout << " export NO_COLOR=1\n";
std::cout << " Tip: When redirecting to a file, colors are automatically disabled:\n";
std::cout << " ./color_ostream > output.txt\n";
std::cout << "\n";
}
int main() {
// First, test color detection
test_color_detection();
// Then demonstrate color features
demonstrate_basic_colors();
demonstrate_combined_colors();
demonstrate_background_colors();
demonstrate_nested_colors();
simulate_colored_debug_output();
demonstrate_multiple_streams();
return 0;
}

View File

@ -0,0 +1,167 @@
#include <CGAL/IO/Indenting_ostream.h>
#include <iostream>
#include <sstream>
/*!
* \example Stream_support/indenting_ostream.cpp
*
* This example demonstrates how to use CGAL::IO::Basic_indenting_streambuf
* and CGAL::IO::basic_indenting_stream_guard for formatting output with
* automatic indentation.
*/
void demonstrate_basic_usage() {
std::cout << "=== Basic Indenting Usage ===\n\n";
// Example 1: Basic indentation with std::cout
{
std::cout << "1. Basic indentation with std::cout:\n";
CGAL::IO::Indenting_stream_guard guard(std::cout, " ");
std::cout << "This line is indented\n";
std::cout << "So is this one\n";
std::cout << "Multi-line output:\n";
std::cout << "Line 1\nLine 2\nLine 3\n";
}
std::cout << "Back to normal indentation\n\n";
// Example 2: Nested indentation levels
{
std::cout << "2. Nested indentation levels:\n";
CGAL::IO::Indenting_stream_guard level1(std::cout, "| ");
std::cout << "Level 1 indentation\n";
{
CGAL::IO::Indenting_stream_guard level2(std::cout, "| ");
std::cout << "Level 2 indentation\n";
{
CGAL::IO::Indenting_stream_guard level3(std::cout, "| ");
std::cout << "Level 3 indentation\n";
}
std::cout << "Back to level 2\n";
}
std::cout << "Back to level 1\n";
}
std::cout << "Back to normal\n\n";
// Example 3: Custom indentation string
{
std::cout << "3. Custom indentation string:\n";
CGAL::IO::Indenting_stream_guard guard(std::cout, ">>> ");
std::cout << "Custom prefix on each line\n";
std::cout << "Works with multiple lines too\n";
}
std::cout << "Normal output again\n\n";
}
void demonstrate_stringstream_usage() {
std::cout << "=== Stringstream Usage ===\n\n";
// Using with stringstream for formatted output
// Note: We create a custom ostream using the indenting streambuf
std::ostringstream backing_stream;
CGAL::IO::Indenting_streambuf indent_buf(*backing_stream.rdbuf(), " ");
std::ostream oss(&indent_buf);
oss << "This goes to stringstream\n";
oss << "With indentation\n";
oss << "Multiple lines\n";
std::cout << "Stringstream contents:\n" << backing_stream.str() << "\n";
}
void simulate_debug_output() {
std::cout << "=== Simulated Debug Output ===\n\n";
std::cout << "Algorithm: Starting geometric computation\n";
{
CGAL::IO::Indenting_stream_guard guard(std::cout, " ");
std::cout << "Phase 1: Input validation\n";
std::cout << "- Checking vertices: OK\n";
std::cout << "- Checking faces: OK\n";
{
CGAL::IO::Indenting_stream_guard inner_guard(std::cout, " ");
std::cout << "Detailed validation:\n";
std::cout << "- Manifold check: PASSED\n";
std::cout << "- Orientation check: PASSED\n";
std::cout << "- Degeneracy check: PASSED\n";
}
std::cout << "Phase 1 complete\n\n";
std::cout << "Phase 2: Triangulation\n";
std::cout << "- Inserting vertices...\n";
std::cout << "- Computing Delaunay triangulation...\n";
std::cout << "- Applying constraints...\n";
{
CGAL::IO::Indenting_stream_guard constraint_guard(std::cout, " ");
std::cout << "Constraint processing:\n";
for(int i = 0; i < 3; ++i) {
std::cout << "- Processing constraint " << i << ": OK\n";
}
}
std::cout << "Phase 2 complete\n";
}
std::cout << "Algorithm: Geometric computation finished successfully\n\n";
}
void demonstrate_multiple_streams() {
std::cout << "=== Multiple Streams with make_indenting_guards ===\n\n";
std::cout << "Before indentation:\n";
std::cout << " cout: Normal output\n";
std::cerr << " cerr: Normal output\n";
// Apply indentation to both streams simultaneously
{
std::cout << "\nWith make_indenting_guards (2 spaces):\n";
auto guards = CGAL::IO::make_indenting_guards(2, std::cout, std::cerr);
std::cout << "This line is indented on cout\n";
std::cout << "And this one too\n";
std::cerr << "This line is indented on cerr\n";
std::cerr << "And this one too\n";
// Nested indentation works too
{
auto nested_guards = CGAL::IO::make_indenting_guards(2, std::cout, std::cerr);
std::cout << "Double indented on cout\n";
std::cerr << "Double indented on cerr\n";
}
std::cout << "Back to single indent\n";
std::cerr << "Back to single indent\n";
}
std::cout << "\nAfter indentation scope:\n";
std::cout << " cout: Normal output again\n";
std::cerr << " cerr: Normal output again\n";
// Using custom indent string with multiple streams
{
std::cout << "\nUsing custom indent string \">> \" with std::cout and std::clog:\n";
auto guards = CGAL::IO::make_indenting_guards(">> ", std::cout, std::clog);
std::cout << "cout: Output with custom indent\n";
std::clog << "clog: Log message with custom indent\n";
std::cout << "cout: More output\n";
}
std::cout << "\n";
}
int main() {
demonstrate_basic_usage();
demonstrate_stringstream_usage();
simulate_debug_output();
demonstrate_multiple_streams();
return 0;
}

View File

@ -0,0 +1,693 @@
// 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
#ifndef CGAL_IO_COLOR_OSTREAM_H
#define CGAL_IO_COLOR_OSTREAM_H
#include <CGAL/config.h>
#include <ios>
#include <streambuf>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#ifdef _WIN32
#include <io.h>
#define CGAL_ISATTY _isatty
#define CGAL_FILENO _fileno
#else
#include <unistd.h>
#define CGAL_ISATTY isatty
#define CGAL_FILENO fileno
#endif
#include <cstdlib> // for getenv
namespace CGAL {
namespace IO {
/**
* \ingroup PkgStreamSupportRef
*
* ANSI color codes for terminal output.
*/
enum class Ansi_color {
Reset = 0,
Bold = 1,
Dim = 2,
Underline = 4,
Blink = 5,
Reverse = 7,
Hidden = 8,
// Foreground colors
Black = 30,
Red = 31,
Green = 32,
Yellow = 33,
Blue = 34,
Magenta = 35,
Cyan = 36,
White = 37,
// Bright foreground colors
BrightBlack = 90,
BrightRed = 91,
BrightGreen = 92,
BrightYellow = 93,
BrightBlue = 94,
BrightMagenta = 95,
BrightCyan = 96,
BrightWhite = 97,
// Background colors
BgBlack = 40,
BgRed = 41,
BgGreen = 42,
BgYellow = 43,
BgBlue = 44,
BgMagenta = 45,
BgCyan = 46,
BgWhite = 47,
// Bright background colors
BgBrightBlack = 100,
BgBrightRed = 101,
BgBrightGreen = 102,
BgBrightYellow = 103,
BgBrightBlue = 104,
BgBrightMagenta = 105,
BgBrightCyan = 106,
BgBrightWhite = 107
};
/**
* \ingroup PkgStreamSupportRef
*
* The class template `Basic_color_streambuf` wraps another `basic_streambuf`
* and automatically adds ANSI color codes to the output. This is useful for
* colorizing terminal output with consistent color schemes.
*
* \tparam CharT Character type (typically `char` or `wchar_t`)
* \tparam Traits Character traits type
*/
template <typename CharT, typename Traits = std::char_traits<CharT>>
class Basic_color_streambuf : public std::basic_streambuf<CharT, Traits>, protected Traits
{
public:
using char_type = CharT;
using traits_type = Traits;
using int_type = typename traits_type::int_type;
using pos_type = typename traits_type::pos_type;
using off_type = typename traits_type::off_type;
using streambuf_type = std::basic_streambuf<char_type, traits_type>;
using string = std::basic_string<char_type>;
private:
streambuf_type* wrapped_buf_;
string color_code_;
bool at_line_start_;
bool colors_enabled_;
/** \brief Detect if the wrapped buffer supports color output. */
static bool detect_color_support(streambuf_type* buf) {
// Check NO_COLOR environment variable first
if(std::getenv("NO_COLOR")) {
return false;
}
// Try to determine if this is stdout or stderr
// by comparing pointers (works for standard streams)
if(buf == std::cout.rdbuf() || buf == std::clog.rdbuf()) {
int fd = CGAL_FILENO(stdout);
if(!CGAL_ISATTY(fd)) {
return false;
}
} else if(buf == std::cerr.rdbuf()) {
int fd = CGAL_FILENO(stderr);
if(!CGAL_ISATTY(fd)) {
return false;
}
} else {
// Unknown buffer, conservatively disable colors
return false;
}
#ifdef _WIN32
// Windows: check for ANSICON or assume modern Windows
return std::getenv("ANSICON") || true;
#else
// POSIX: check TERM environment variable
const char* term = std::getenv("TERM");
if(!term) {
return false;
}
std::string term_str(term);
if(term_str == "dumb") {
return false;
}
// Check for common color-capable terminals
return term_str.find("color") != std::string::npos ||
term_str.find("xterm") != std::string::npos ||
term_str.find("rxvt") != std::string::npos ||
term_str.find("screen") != std::string::npos ||
term_str.find("tmux") != std::string::npos ||
term_str.find("linux") != std::string::npos;
#endif
}
/** \brief Generate ANSI escape sequence for a color. */
static string make_color_code(Ansi_color color) {
string code;
code += char_type('\033');
code += char_type('[');
int color_value = static_cast<int>(color);
// Convert integer to string
if(color_value == 0) {
code += char_type('0');
} else {
string digits;
while(color_value > 0) {
digits = char_type('0' + (color_value % 10)) + digits;
color_value /= 10;
}
code += digits;
}
code += char_type('m');
return code;
}
public:
/**
* \brief Construct a color streambuf wrapper.
*
* Colors are automatically disabled if the wrapped buffer is not connected
* to a color-capable terminal (checked via isatty() and TERM variable).
*
* \param wrapped_buf The underlying streambuf to wrap
* \param color The color to apply to the output
*/
explicit
Basic_color_streambuf(streambuf_type& wrapped_buf,
Ansi_color color = Ansi_color::Reset)
: wrapped_buf_(&wrapped_buf)
, color_code_(make_color_code(color))
, at_line_start_(true)
, colors_enabled_(detect_color_support(&wrapped_buf))
{
}
/**
* \brief Construct a color streambuf wrapper with multiple colors.
*
* Colors are automatically disabled if the wrapped buffer is not connected
* to a color-capable terminal (checked via isatty() and TERM variable).
*
* \param wrapped_buf The underlying streambuf to wrap
* \param colors Vector of colors to combine (e.g., bold + red)
*/
Basic_color_streambuf(streambuf_type& wrapped_buf,
const std::vector<Ansi_color>& colors)
: wrapped_buf_(&wrapped_buf)
, at_line_start_(true)
, colors_enabled_(detect_color_support(&wrapped_buf))
{
if(!colors.empty()) {
color_code_ += char_type('\033');
color_code_ += char_type('[');
for(std::size_t i = 0; i < colors.size(); ++i) {
if(i > 0) color_code_ += char_type(';');
int color_value = static_cast<int>(colors[i]);
// Convert integer to string
if(color_value == 0) {
color_code_ += char_type('0');
} else {
string digits;
while(color_value > 0) {
digits = char_type('0' + (color_value % 10)) + digits;
color_value /= 10;
}
color_code_ += digits;
}
}
color_code_ += char_type('m');
}
}
// Non-copyable
Basic_color_streambuf(const Basic_color_streambuf&) = delete;
Basic_color_streambuf& operator=(const Basic_color_streambuf&) = delete;
// Move constructor
Basic_color_streambuf(Basic_color_streambuf&& other) noexcept
: wrapped_buf_(std::exchange(other.wrapped_buf_, nullptr))
, color_code_(std::move(other.color_code_))
, at_line_start_(std::exchange(other.at_line_start_, true))
, colors_enabled_(std::exchange(other.colors_enabled_, false))
{
}
// Move assignment
Basic_color_streambuf& operator=(Basic_color_streambuf&& other) noexcept {
if(this != &other) {
// If currently wrapping a buffer, try to sync it first.
if(wrapped_buf_) {
wrapped_buf_->pubsync();
}
wrapped_buf_ = std::exchange(other.wrapped_buf_, nullptr);
color_code_ = std::move(other.color_code_);
at_line_start_ = std::exchange(other.at_line_start_, true);
colors_enabled_ = std::exchange(other.colors_enabled_, false);
}
return *this;
}
~Basic_color_streambuf() noexcept {
if(wrapped_buf_) {
if(colors_enabled_ && !color_code_.empty() && !at_line_start_) {
string reset_code = make_color_code(Ansi_color::Reset);
(void)put_a_string(reset_code);
}
wrapped_buf_->pubsync();
}
}
/** \brief Get the current color code string. */
const string& color_code() const { return color_code_; }
/**
* \brief Set a new color code.
*
* \param string The new color code to apply
*/
void set_color_code(const string& color_code) {
color_code_ = color_code;
}
/** \brief Check if colors are enabled for this streambuf. */
bool colors_enabled() const { return colors_enabled_; }
/** \brief Get 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;
bool put_a_string(const string& str) {
for(char_type ch : str) {
if(eq_int_type(wrapped_buf_->sputc(ch), eof())) {
return false;
}
}
return true;
}
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 color code first (if colors enabled)
if(colors_enabled_ && at_line_start_ && !color_code_.empty()) {
if(!put_a_string(color_code_)) {
return eof();
}
at_line_start_ = false;
}
// Check if this character is a newline
if(to_char_type(ch) == char_type('\n')) {
// Output reset code before newline (if colors enabled)
if(colors_enabled_ && !color_code_.empty()) {
string reset_code = make_color_code(Ansi_color::Reset);
if(!put_a_string(reset_code)) {
return eof();
}
}
at_line_start_ = true;
}
// Output the actual character
return wrapped_buf_->sputc(to_char_type(ch));
}
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);
}
};
/// Type alias for `Basic_color_streambuf<char>`
using Color_streambuf = Basic_color_streambuf<char>;
/// Type alias for `Basic_color_streambuf<wchar_t>`
using Color_wstreambuf = Basic_color_streambuf<wchar_t>;
/**
* \ingroup PkgStreamSupportRef
*
* RAII helper class for temporarily installing a color streambuf on a stream.
*
* This class automatically restores the original streambuf when it goes out of scope.
* It provides a convenient way to add colors to output streams within a specific scope.
*
* \tparam StreamT The stream type (e.g., `std::ostream`, `std::wostream`)
*/
template <typename StreamT>
class Basic_color_stream_guard
{
private:
using char_type = typename StreamT::char_type;
using traits_type = typename StreamT::traits_type;
using streambuf_type = Basic_color_streambuf<char_type, traits_type>;
using wrapped_streambuf_type = std::basic_streambuf<char_type, traits_type>;
using string = std::basic_string<char_type>;
StreamT& stream_;
wrapped_streambuf_type* original_buf_;
string original_color_code_;
streambuf_type color_buf_;
public:
/**
* \brief Construct and install a color streambuf on the given stream.
*
* \param stream The stream to modify
* \param color The color to apply to the output
*/
explicit Basic_color_stream_guard(StreamT& stream,
Ansi_color color)
: stream_(stream)
, original_buf_(stream.rdbuf())
, color_buf_(*original_buf_, color)
{
if(auto old_buf = dynamic_cast<streambuf_type*>(original_buf_)) {
original_color_code_ = old_buf->color_code();
// For nested colors, we keep the current buffer but note the original color
} else {
stream.rdbuf(&color_buf_);
}
}
/**
* \brief Construct and install a color streambuf with multiple colors.
*
* \param stream The stream to modify
* \param colors Vector of colors to combine (e.g., bold + red)
*/
Basic_color_stream_guard(StreamT& stream,
const std::vector<Ansi_color>& colors)
: stream_(stream)
, original_buf_(stream.rdbuf())
, color_buf_(*original_buf_, colors)
{
if(auto old_buf = dynamic_cast<streambuf_type*>(original_buf_)) {
original_color_code_ = old_buf->color_code();
} else {
stream.rdbuf(&color_buf_);
}
}
// Non-copyable, movable
Basic_color_stream_guard(const Basic_color_stream_guard&) = delete;
Basic_color_stream_guard& operator=(const Basic_color_stream_guard&) = delete;
Basic_color_stream_guard(Basic_color_stream_guard&& other)
: stream_(other.stream_)
, original_buf_(other.original_buf_)
, original_color_code_(std::move(other.original_color_code_))
, color_buf_(std::move(other.color_buf_))
{
// Update the stream to point to the new buffer location if it was using the old one
if(stream_.rdbuf() == &other.color_buf_) {
stream_.rdbuf(&color_buf_);
}
// Mark other as moved-from so it won't restore the stream
other.original_buf_ = nullptr;
}
Basic_color_stream_guard& operator=(Basic_color_stream_guard&&) = delete;
/** \brief Destructor - restores the original streambuf. */
~Basic_color_stream_guard() {
if(!original_buf_) return; // Moved-from object
if(auto old_buf = dynamic_cast<streambuf_type*>(original_buf_)) {
old_buf->set_color_code(original_color_code_);
} else {
stream_.rdbuf(original_buf_);
}
}
};
/// Type alias for `Basic_color_stream_guard<std::ostream>`
using Color_stream_guard = Basic_color_stream_guard<std::ostream>;
/// Type alias for `Basic_color_stream_guard<std::wostream>`
using Color_wstream_guard = Basic_color_stream_guard<std::wostream>;
/**
* \ingroup PkgStreamSupportRef
*
* \brief Create color guards for multiple streams simultaneously.
*
* This helper function creates `Basic_color_stream_guard` objects for
* multiple streams at once. All streams will use the same color 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 color The color to apply
* \param streams The streams to apply colors to
* \return A tuple of Basic_color_stream_guard objects
*
* Example usage:
* \code
* auto guards = CGAL::make_color_guards(Color::Red, std::cout, std::cerr);
* std::cout << "Red output\n";
* std::cerr << "Red error\n";
* \endcode
*/
template <typename... Streams>
auto make_color_guards(Ansi_color color, Streams&... streams) {
return std::make_tuple(Basic_color_stream_guard<Streams>(streams, color)...);
}
/**
* \ingroup PkgStreamSupportRef
*
* \brief Create color guards for multiple streams with multiple colors.
*
* This overload allows specifying multiple colors to combine (e.g., bold + red).
*
* \tparam Streams The stream types (deduced from arguments)
* \param colors Vector of colors to combine
* \param streams The streams to apply colors to
* \return A tuple of Basic_color_stream_guard objects
*/
template <typename... Streams>
auto make_color_guards(const std::vector<Ansi_color>& colors, Streams&... streams) {
return std::make_tuple(Basic_color_stream_guard<Streams>(streams, colors)...);
}
/**
* \ingroup PkgStreamSupportRef
*
* \brief Check if a stream is attached to a terminal that supports colors.
*
* This function checks if the given output stream is connected to a terminal
* (TTY) that can display ANSI color codes. It performs the following checks:
*
* 1. Verifies the stream is attached to a TTY device using `isatty()`
* 2. Checks if the `TERM` environment variable indicates color support
* 3. Respects the `NO_COLOR` environment variable (if set, colors are disabled)
* 4. On Windows, also checks for `ANSICON` environment variable
*
* \param stream The output stream to check (e.g., `std::cout`, `std::cerr`)
* \return `true` if the stream supports color output, `false` otherwise
*
* \note This function may return false negatives on some systems, but should
* never return false positives (it won't report color support where
* it doesn't exist).
*
* Example usage:
* \code
* if(CGAL::stream_supports_color(std::cout)) {
* CGAL::Color_stream_guard guard(std::cout, CGAL::Ansi_color::Red);
* std::cout << "This text is red!\n";
* } else {
* std::cout << "Plain text output\n";
* }
* \endcode
*/
template <typename CharT, typename Traits>
bool stream_supports_color(const std::basic_ostream<CharT, Traits>& stream) {
// Check if NO_COLOR environment variable is set (standard way to disable colors)
if(std::getenv("NO_COLOR")) {
return false;
}
// Try to get the file descriptor for the stream
// This works for std::cout (stdout) and std::cerr (stderr)
const auto* fbuf = dynamic_cast<const std::basic_filebuf<CharT, Traits>*>(stream.rdbuf());
if(!fbuf) {
// If it's not a file buffer, check if it's stdout or stderr directly
const std::basic_ostream<CharT, Traits>* std_stream = &stream;
int fd = -1;
if(std_stream == &std::cout || std_stream == &std::clog) {
fd = CGAL_FILENO(stdout);
} else if(std_stream == &std::cerr) {
fd = CGAL_FILENO(stderr);
}
if(fd == -1) {
return false; // Unknown stream type
}
// Check if the file descriptor is a TTY
if(!CGAL_ISATTY(fd)) {
return false;
}
} else {
// For file buffers, we can't easily get the fd in a portable way
// Conservatively assume no color support for files
return false;
}
#ifdef _WIN32
// On Windows, check for ANSICON or Windows 10+ with VT100 support
// Modern Windows Terminal and ConEmu support ANSI colors
if(std::getenv("ANSICON")) {
return true;
}
// Windows 10+ console supports ANSI by default
// We could check Windows version, but being conservative here
return true; // Modern Windows usually supports colors
#else
// On POSIX systems, check the TERM environment variable
const char* term = std::getenv("TERM");
if(!term) {
return false; // No TERM variable, probably not a color terminal
}
std::string term_str(term);
// Check for common indicators of color support
if(term_str.find("color") != std::string::npos ||
term_str.find("xterm") != std::string::npos ||
term_str.find("rxvt") != std::string::npos ||
term_str.find("screen") != std::string::npos ||
term_str.find("tmux") != std::string::npos ||
term_str.find("linux") != std::string::npos) {
return true;
}
// Check for dumb terminal
if(term_str == "dumb") {
return false;
}
// For other TERM values, conservatively assume color support
// Most modern terminals support colors
return true;
#endif
}
/**
* \ingroup PkgStreamSupportRef
*
* \brief Check if stdout supports color output.
*
* Convenience function equivalent to `stream_supports_color(std::cout)`.
*
* \return `true` if stdout supports color output, `false` otherwise
*/
inline bool stdout_supports_color() {
return stream_supports_color(std::cout);
}
/**
* \ingroup PkgStreamSupportRef
*
* \brief Check if stderr supports color output.
*
* Convenience function equivalent to `stream_supports_color(std::cerr)`.
*
* \return `true` if stderr supports color output, `false` otherwise
*/
inline bool stderr_supports_color() {
return stream_supports_color(std::cerr);
}
} // namespace IO
} // namespace CGAL
#endif // CGAL_IO_COLOR_OSTREAM_H

View File

@ -0,0 +1,330 @@
// 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
#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
*/
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;
using traits_type = Traits;
using int_type = typename traits_type::int_type;
using pos_type = typename traits_type::pos_type;
using off_type = typename traits_type::off_type;
using streambuf_type = std::basic_streambuf<char_type, traits_type>;
using string = std::basic_string<char_type>;
private:
streambuf_type* wrapped_buf_;
string indent_string_;
bool at_line_start_;
public:
/**
* \brief Construct 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 Get the current indentation string. */
const string& indent_string() const { return indent_string_; }
/**
* \brief Set a new indentation string.
*
* \param new_indent The new indentation string
*/
void set_indent_string(const string& new_indent) { indent_string_ = new_indent; }
/**
* \brief Set 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 Get 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);
}
};
/// Type alias for `Basic_indenting_streambuf<char>`
using Indenting_streambuf = Basic_indenting_streambuf<char>;
/// 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`)
*/
template <typename StreamT>
class Basic_indenting_stream_guard
{
private:
using char_type = typename StreamT::char_type;
using traits_type = typename StreamT::traits_type;
using streambuf_type = Basic_indenting_streambuf<char_type, traits_type>;
using wrapped_streambuf_type = std::basic_streambuf<char_type, traits_type>;
using string = std::basic_string<char_type>;
StreamT& stream_;
wrapped_streambuf_type* original_buf_;
string original_indent_string_;
streambuf_type indenting_buf_;
public:
/**
* \brief Construct and install 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 Construct and install 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_);
}
}
};
/// Type alias for `basic_indenting_stream_guard<std::ostream>`
using Indenting_stream_guard = Basic_indenting_stream_guard<std::ostream>;
/// Type alias for `basic_indenting_stream_guard<std::wostream>`
using Indenting_wstream_guard = Basic_indenting_stream_guard<std::wostream>;
/**
* \ingroup PkgStreamSupportRef
*
* \brief Create 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
*
* Example usage:
* \code
* auto guards = CGAL::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 Create 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
*/
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