mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
492 lines
13 KiB
C++
492 lines
13 KiB
C++
/** A Minimal, Header only Modern c++ library for terminal goodies
|
|
*
|
|
* See: https://github.com/agauniyal/rang
|
|
*
|
|
* SPDX-FileCopyrightText: 2022 Abhinav Gauniyal
|
|
* SPDX-License-Identifier: Unlicense
|
|
*********************************************************************************/
|
|
|
|
#ifndef RANG_DOT_HPP
|
|
#define RANG_DOT_HPP
|
|
|
|
#if defined(__unix__) || defined(__unix) || defined(__linux__)
|
|
#define OS_LINUX
|
|
#elif defined(WIN32) || defined(_WIN32) || defined(_WIN64)
|
|
#define OS_WIN
|
|
#elif defined(__APPLE__) || defined(__MACH__)
|
|
#define OS_MAC
|
|
#else
|
|
#error Unknown Platform
|
|
#endif
|
|
|
|
#if defined(OS_LINUX) || defined(OS_MAC)
|
|
#include <unistd.h>
|
|
|
|
#elif defined(OS_WIN)
|
|
|
|
#if defined(_WIN32_WINNT) && (_WIN32_WINNT < 0x0600)
|
|
#error \
|
|
"Please include rang.hpp before any windows system headers or set _WIN32_WINNT at least to _WIN32_WINNT_VISTA"
|
|
#elif !defined(_WIN32_WINNT)
|
|
#define _WIN32_WINNT _WIN32_WINNT_VISTA
|
|
#endif
|
|
|
|
#include <io.h>
|
|
#include <memory>
|
|
#include <windows.h>
|
|
|
|
// Only defined in windows 10 onwards, redefining in lower windows since it
|
|
// doesn't gets used in lower versions
|
|
// https://docs.microsoft.com/en-us/windows/console/getconsolemode
|
|
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
|
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
|
|
namespace rang {
|
|
|
|
/* For better compability with most of terminals do not use any style settings
|
|
* except of reset, bold and reversed.
|
|
* Note that on Windows terminals bold style is same as fgB color.
|
|
*/
|
|
enum class style {
|
|
reset = 0,
|
|
bold = 1,
|
|
dim = 2,
|
|
italic = 3,
|
|
underline = 4,
|
|
blink = 5,
|
|
rblink = 6,
|
|
reversed = 7,
|
|
conceal = 8,
|
|
crossed = 9
|
|
};
|
|
|
|
enum class fg {
|
|
black = 30,
|
|
red = 31,
|
|
green = 32,
|
|
yellow = 33,
|
|
blue = 34,
|
|
magenta = 35,
|
|
cyan = 36,
|
|
gray = 37,
|
|
reset = 39
|
|
};
|
|
|
|
enum class bg {
|
|
black = 40,
|
|
red = 41,
|
|
green = 42,
|
|
yellow = 43,
|
|
blue = 44,
|
|
magenta = 45,
|
|
cyan = 46,
|
|
gray = 47,
|
|
reset = 49
|
|
};
|
|
|
|
enum class fgB {
|
|
black = 90,
|
|
red = 91,
|
|
green = 92,
|
|
yellow = 93,
|
|
blue = 94,
|
|
magenta = 95,
|
|
cyan = 96,
|
|
gray = 97
|
|
};
|
|
|
|
enum class bgB {
|
|
black = 100,
|
|
red = 101,
|
|
green = 102,
|
|
yellow = 103,
|
|
blue = 104,
|
|
magenta = 105,
|
|
cyan = 106,
|
|
gray = 107
|
|
};
|
|
|
|
enum class control { // Behaviour of rang function calls
|
|
Off = 0, // toggle off rang style/color calls
|
|
Auto = 1, // (Default) autodect terminal and colorize if needed
|
|
Force = 2 // force ansi color output to non terminal streams
|
|
};
|
|
// Use rang::setControlMode to set rang control mode
|
|
|
|
enum class winTerm { // Windows Terminal Mode
|
|
Auto = 0, // (Default) automatically detects wheter Ansi or Native API
|
|
Ansi = 1, // Force use Ansi API
|
|
Native = 2 // Force use Native API
|
|
};
|
|
// Use rang::setWinTermMode to explicitly set terminal API for Windows
|
|
// Calling rang::setWinTermMode have no effect on other OS
|
|
|
|
namespace rang_implementation {
|
|
|
|
inline std::atomic<control> &controlMode() noexcept {
|
|
static std::atomic<control> value(control::Auto);
|
|
return value;
|
|
}
|
|
|
|
inline std::atomic<winTerm> &winTermMode() noexcept {
|
|
static std::atomic<winTerm> termMode(winTerm::Auto);
|
|
return termMode;
|
|
}
|
|
|
|
inline bool supportsColor() noexcept {
|
|
#if defined(OS_LINUX) || defined(OS_MAC)
|
|
|
|
static const bool result = [] {
|
|
const char *Terms[] = {"ansi", "color", "console", "cygwin", "gnome",
|
|
"konsole", "kterm", "linux", "msys", "putty",
|
|
"rxvt", "screen", "vt100", "xterm"};
|
|
|
|
const char *env_p = std::getenv("TERM");
|
|
if (env_p == nullptr) {
|
|
return false;
|
|
}
|
|
return std::any_of(
|
|
std::begin(Terms), std::end(Terms),
|
|
[&](const char *term) { return std::strstr(env_p, term) != nullptr; });
|
|
}();
|
|
|
|
#elif defined(OS_WIN)
|
|
// All windows versions support colors through native console methods
|
|
static constexpr bool result = true;
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
#ifdef OS_WIN
|
|
|
|
inline bool isMsysPty(int fd) noexcept {
|
|
// Dynamic load for binary compability with old Windows
|
|
const auto ptrGetFileInformationByHandleEx =
|
|
reinterpret_cast<decltype(&GetFileInformationByHandleEx)>(
|
|
GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")),
|
|
"GetFileInformationByHandleEx"));
|
|
if (!ptrGetFileInformationByHandleEx) {
|
|
return false;
|
|
}
|
|
|
|
HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
|
|
if (h == INVALID_HANDLE_VALUE) {
|
|
return false;
|
|
}
|
|
|
|
// Check that it's a pipe:
|
|
if (GetFileType(h) != FILE_TYPE_PIPE) {
|
|
return false;
|
|
}
|
|
|
|
// POD type is binary compatible with FILE_NAME_INFO from WinBase.h
|
|
// It have the same alignment and used to avoid UB in caller code
|
|
struct MY_FILE_NAME_INFO {
|
|
DWORD FileNameLength;
|
|
WCHAR FileName[MAX_PATH];
|
|
};
|
|
|
|
auto pNameInfo = std::unique_ptr<MY_FILE_NAME_INFO>(new (std::nothrow)
|
|
MY_FILE_NAME_INFO());
|
|
if (!pNameInfo) {
|
|
return false;
|
|
}
|
|
|
|
// Check pipe name is template of
|
|
// {"cygwin-","msys-"}XXXXXXXXXXXXXXX-ptyX-XX
|
|
if (!ptrGetFileInformationByHandleEx(h, FileNameInfo, pNameInfo.get(),
|
|
sizeof(MY_FILE_NAME_INFO))) {
|
|
return false;
|
|
}
|
|
std::wstring name(pNameInfo->FileName,
|
|
pNameInfo->FileNameLength / sizeof(WCHAR));
|
|
if ((name.find(L"msys-") == std::wstring::npos &&
|
|
name.find(L"cygwin-") == std::wstring::npos) ||
|
|
name.find(L"-pty") == std::wstring::npos) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
inline bool isTerminal(const std::streambuf *osbuf) noexcept {
|
|
using std::cerr;
|
|
using std::clog;
|
|
using std::cout;
|
|
#if defined(OS_LINUX) || defined(OS_MAC)
|
|
if (osbuf == cout.rdbuf()) {
|
|
static const bool cout_term = isatty(fileno(stdout)) != 0;
|
|
return cout_term;
|
|
} else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) {
|
|
static const bool cerr_term = isatty(fileno(stderr)) != 0;
|
|
return cerr_term;
|
|
}
|
|
#elif defined(OS_WIN)
|
|
if (osbuf == cout.rdbuf()) {
|
|
static const bool cout_term =
|
|
(_isatty(_fileno(stdout)) || isMsysPty(_fileno(stdout)));
|
|
return cout_term;
|
|
} else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) {
|
|
static const bool cerr_term =
|
|
(_isatty(_fileno(stderr)) || isMsysPty(_fileno(stderr)));
|
|
return cerr_term;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
using enableStd = typename std::enable_if<
|
|
std::is_same<T, rang::style>::value || std::is_same<T, rang::fg>::value ||
|
|
std::is_same<T, rang::bg>::value || std::is_same<T, rang::fgB>::value ||
|
|
std::is_same<T, rang::bgB>::value,
|
|
std::ostream &>::type;
|
|
|
|
#ifdef OS_WIN
|
|
|
|
struct SGR { // Select Graphic Rendition parameters for Windows console
|
|
BYTE fgColor; // foreground color (0-15) lower 3 rgb bits + intense bit
|
|
BYTE bgColor; // background color (0-15) lower 3 rgb bits + intense bit
|
|
BYTE bold; // emulated as FOREGROUND_INTENSITY bit
|
|
BYTE underline; // emulated as BACKGROUND_INTENSITY bit
|
|
BOOLEAN inverse; // swap foreground/bold & background/underline
|
|
BOOLEAN conceal; // set foreground/bold to background/underline
|
|
};
|
|
|
|
enum class AttrColor : BYTE { // Color attributes for console screen buffer
|
|
black = 0,
|
|
red = 4,
|
|
green = 2,
|
|
yellow = 6,
|
|
blue = 1,
|
|
magenta = 5,
|
|
cyan = 3,
|
|
gray = 7
|
|
};
|
|
|
|
inline HANDLE getConsoleHandle(const std::streambuf *osbuf) noexcept {
|
|
if (osbuf == std::cout.rdbuf()) {
|
|
static const HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
return hStdout;
|
|
} else if (osbuf == std::cerr.rdbuf() || osbuf == std::clog.rdbuf()) {
|
|
static const HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE);
|
|
return hStderr;
|
|
}
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
inline bool setWinTermAnsiColors(const std::streambuf *osbuf) noexcept {
|
|
HANDLE h = getConsoleHandle(osbuf);
|
|
if (h == INVALID_HANDLE_VALUE) {
|
|
return false;
|
|
}
|
|
DWORD dwMode = 0;
|
|
if (!GetConsoleMode(h, &dwMode)) {
|
|
return false;
|
|
}
|
|
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
if (!SetConsoleMode(h, dwMode)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline bool supportsAnsi(const std::streambuf *osbuf) noexcept {
|
|
using std::cerr;
|
|
using std::clog;
|
|
using std::cout;
|
|
if (osbuf == cout.rdbuf()) {
|
|
static const bool cout_ansi =
|
|
(isMsysPty(_fileno(stdout)) || setWinTermAnsiColors(osbuf));
|
|
return cout_ansi;
|
|
} else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) {
|
|
static const bool cerr_ansi =
|
|
(isMsysPty(_fileno(stderr)) || setWinTermAnsiColors(osbuf));
|
|
return cerr_ansi;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline const SGR &defaultState() noexcept {
|
|
static const SGR defaultSgr = []() -> SGR {
|
|
CONSOLE_SCREEN_BUFFER_INFO info;
|
|
WORD attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
|
|
if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info) ||
|
|
GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE), &info)) {
|
|
attrib = info.wAttributes;
|
|
}
|
|
SGR sgr = {0, 0, 0, 0, FALSE, FALSE};
|
|
sgr.fgColor = attrib & 0x0F;
|
|
sgr.bgColor = (attrib & 0xF0) >> 4;
|
|
return sgr;
|
|
}();
|
|
return defaultSgr;
|
|
}
|
|
|
|
inline BYTE ansi2attr(BYTE rgb) noexcept {
|
|
static const AttrColor rev[8] = {
|
|
AttrColor::black, AttrColor::red, AttrColor::green, AttrColor::yellow,
|
|
AttrColor::blue, AttrColor::magenta, AttrColor::cyan, AttrColor::gray};
|
|
return static_cast<BYTE>(rev[rgb]);
|
|
}
|
|
|
|
inline void setWinSGR(rang::bg col, SGR &state) noexcept {
|
|
if (col != rang::bg::reset) {
|
|
state.bgColor = ansi2attr(static_cast<BYTE>(col) - 40);
|
|
} else {
|
|
state.bgColor = defaultState().bgColor;
|
|
}
|
|
}
|
|
|
|
inline void setWinSGR(rang::fg col, SGR &state) noexcept {
|
|
if (col != rang::fg::reset) {
|
|
state.fgColor = ansi2attr(static_cast<BYTE>(col) - 30);
|
|
} else {
|
|
state.fgColor = defaultState().fgColor;
|
|
}
|
|
}
|
|
|
|
inline void setWinSGR(rang::bgB col, SGR &state) noexcept {
|
|
state.bgColor =
|
|
(BACKGROUND_INTENSITY >> 4) | ansi2attr(static_cast<BYTE>(col) - 100);
|
|
}
|
|
|
|
inline void setWinSGR(rang::fgB col, SGR &state) noexcept {
|
|
state.fgColor = FOREGROUND_INTENSITY | ansi2attr(static_cast<BYTE>(col) - 90);
|
|
}
|
|
|
|
inline void setWinSGR(rang::style style, SGR &state) noexcept {
|
|
switch (style) {
|
|
case rang::style::reset:
|
|
state = defaultState();
|
|
break;
|
|
case rang::style::bold:
|
|
state.bold = FOREGROUND_INTENSITY;
|
|
break;
|
|
case rang::style::underline:
|
|
case rang::style::blink:
|
|
state.underline = BACKGROUND_INTENSITY;
|
|
break;
|
|
case rang::style::reversed:
|
|
state.inverse = TRUE;
|
|
break;
|
|
case rang::style::conceal:
|
|
state.conceal = TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
inline SGR ¤t_state() noexcept {
|
|
static SGR state = defaultState();
|
|
return state;
|
|
}
|
|
|
|
inline WORD SGR2Attr(const SGR &state) noexcept {
|
|
WORD attrib = 0;
|
|
if (state.conceal) {
|
|
if (state.inverse) {
|
|
attrib = (state.fgColor << 4) | state.fgColor;
|
|
if (state.bold)
|
|
attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;
|
|
} else {
|
|
attrib = (state.bgColor << 4) | state.bgColor;
|
|
if (state.underline)
|
|
attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;
|
|
}
|
|
} else if (state.inverse) {
|
|
attrib = (state.fgColor << 4) | state.bgColor;
|
|
if (state.bold)
|
|
attrib |= BACKGROUND_INTENSITY;
|
|
if (state.underline)
|
|
attrib |= FOREGROUND_INTENSITY;
|
|
} else {
|
|
attrib =
|
|
state.fgColor | (state.bgColor << 4) | state.bold | state.underline;
|
|
}
|
|
return attrib;
|
|
}
|
|
|
|
template <typename T>
|
|
inline void setWinColorAnsi(std::ostream &os, T const value) {
|
|
os << "\033[" << static_cast<int>(value) << "m";
|
|
}
|
|
|
|
template <typename T>
|
|
inline void setWinColorNative(std::ostream &os, T const value) {
|
|
const HANDLE h = getConsoleHandle(os.rdbuf());
|
|
if (h != INVALID_HANDLE_VALUE) {
|
|
setWinSGR(value, current_state());
|
|
// Out all buffered text to console with previous settings:
|
|
os.flush();
|
|
SetConsoleTextAttribute(h, SGR2Attr(current_state()));
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
inline enableStd<T> setColor(std::ostream &os, T const value) {
|
|
if (winTermMode() == winTerm::Auto) {
|
|
if (supportsAnsi(os.rdbuf())) {
|
|
setWinColorAnsi(os, value);
|
|
} else {
|
|
setWinColorNative(os, value);
|
|
}
|
|
} else if (winTermMode() == winTerm::Ansi) {
|
|
setWinColorAnsi(os, value);
|
|
} else {
|
|
setWinColorNative(os, value);
|
|
}
|
|
return os;
|
|
}
|
|
#else
|
|
template <typename T>
|
|
inline enableStd<T> setColor(std::ostream &os, T const value) {
|
|
return os << "\033[" << static_cast<int>(value) << "m";
|
|
}
|
|
#endif
|
|
} // namespace rang_implementation
|
|
|
|
template <typename T>
|
|
inline rang_implementation::enableStd<T> operator<<(std::ostream &os,
|
|
const T value) {
|
|
const control option = rang_implementation::controlMode();
|
|
switch (option) {
|
|
case control::Auto:
|
|
return rang_implementation::supportsColor() &&
|
|
rang_implementation::isTerminal(os.rdbuf())
|
|
? rang_implementation::setColor(os, value)
|
|
: os;
|
|
case control::Force:
|
|
return rang_implementation::setColor(os, value);
|
|
default:
|
|
return os;
|
|
}
|
|
}
|
|
|
|
inline void setWinTermMode(const rang::winTerm value) noexcept {
|
|
rang_implementation::winTermMode() = value;
|
|
}
|
|
|
|
inline void setControlMode(const control value) noexcept {
|
|
rang_implementation::controlMode() = value;
|
|
}
|
|
|
|
} // namespace rang
|
|
|
|
#undef OS_LINUX
|
|
#undef OS_WIN
|
|
#undef OS_MAC
|
|
|
|
#endif /* ifndef RANG_DOT_HPP */
|