mirror of https://github.com/CGAL/cgal
Stream_support: add indenting and coloring stream wrappers
This commit is contained in:
parent
b3e2f204a4
commit
eaea32ee9f
|
|
@ -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" )
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue