I went from manually outputting escape codes in each string to creating a utility struct/class full of static functions and variables that needed to be initialized in source files (static private members are required to) which was a bit ugly for me. Then I was searching for something different and while playing with enums, I found it interesting for my scenario. And then I carefully used namespaces to construct my sort-of function only header and here it is. Also, I'm not supporting pre-C++11 version of compilers.
My aims were – not to use classes since they became ugly with lots of static members and creating an object and using it for colouring doesn't looks so good (however I might try that in future), and to make it fast and also modern C++ish. Along the way, some neat tricks with C++ namespaces made it possible to sort of hide (well at least to code completions) my non-interface elements and give a nice shape to the code.
*I also want to reduce some not so necessary headers (e.g. <cstring>) if possible.
#ifndef RANG_H
#define RANG_H
#include <iostream>
#include <cstdlib>
#include <cstring>
extern "C" {
#include <unistd.h>
}
namespace rang {
enum class style : unsigned char {
Reset = 0,
bold = 1,
dim = 2,
italic = 3,
underline = 4,
blink = 5,
reversed = 6,
conceal = 7,
crossed = 8
};
enum class fg : unsigned char {
def = 39,
black = 30,
red = 31,
green = 32,
yellow = 33,
blue = 34,
magenta = 35,
cyan = 36,
gray = 37
};
enum class bg : unsigned char {
def = 49,
black = 40,
red = 41,
green = 42,
yellow = 43,
blue = 44,
magenta = 45,
cyan = 46,
gray = 47
};
}
namespace {
bool isAllowed = false;
bool isTerminal()
{
return isatty(STDOUT_FILENO);
}
bool supportsColor()
{
if(const char *env_p = std::getenv("TERM")) {
const char *const term[8] = {
"xterm", "xterm-256", "xterm-256color", "vt100",
"color", "ansi", "cygwin", "linux"};
for(unsigned int i = 0; i < 8; ++i) {
if(std::strcmp(env_p, term[i]) == 0) return true;
}
}
return false;
}
std::ostream &operator<<(std::ostream &os, rang::style v)
{
return isAllowed ? os << "\e[" << static_cast<int>(v) << "m" : os;
}
std::ostream &operator<<(std::ostream &os, rang::fg v)
{
return isAllowed ? os << "\e[" << static_cast<int>(v) << "m" : os;
}
std::ostream &operator<<(std::ostream &os, rang::bg v)
{
return isAllowed ? os << "\e[" << static_cast<int>(v) << "m" : os;
}
namespace init {
void rang()
{
isAllowed = isTerminal() && supportsColor() ? true : false;
}
}
}
#endif /* ifndef RANG_H*/
And to test:
#include <iostream>
#include "rang.h"
int main()
{
init::rang();
std::cout << rang::style::bold << rang::fg::red << "ERROR HERE! "
<< std::endl
<< rang::bg::red << rang::fg::gray << "ERROR INVERSE?"
<< rang::style::Reset << std::endl;
return 0;
}
And here are warnings if you don't want to compile:
agauniyal@lenovo > rang[master] » clang++ test.cpp -std=c++11 -o rang.app -Wall -Weverything In file included from test.cpp:2: ./rang.h:13:7: warning: scoped enumerations are incompatible with C++98 [-Wc++98-compat] enum class style : unsigned char { ^ ./rang.h:13:2: warning: enumeration types with a fixed underlying type are incompatible with C++98 [-Wc++98-compat] enum class style : unsigned char { ^ ./rang.h:24:7: warning: scoped enumerations are incompatible with C++98 [-Wc++98-compat] enum class fg : unsigned char { ^ ./rang.h:24:2: warning: enumeration types with a fixed underlying type are incompatible with C++98 [-Wc++98-compat] enum class fg : unsigned char { ^ ./rang.h:35:7: warning: scoped enumerations are incompatible with C++98 [-Wc++98-compat] enum class bg : unsigned char { ^ ./rang.h:35:2: warning: enumeration types with a fixed underlying type are incompatible with C++98 [-Wc++98-compat] enum class bg : unsigned char { ^ ./rang.h:69:29: warning: use of non-standard escape character '\e' [-Wpedantic] return isAllowed ? os << "\e[" << static_cast<int>(v) << "m" : os; ^~ ./rang.h:73:29: warning: use of non-standard escape character '\e' [-Wpedantic] return isAllowed ? os << "\e[" << static_cast<int>(v) << "m" : os; ^~ ./rang.h:77:29: warning: use of non-standard escape character '\e' [-Wpedantic] return isAllowed ? os << "\e[" << static_cast<int>(v) << "m" : os; ^~ ./rang.h:80:8: warning: no previous prototype for function 'rang' [-Wmissing-prototypes] void rang() ^ test.cpp:7:21: warning: enumeration type in nested name specifier is incompatible with C++98 [-Wc++98-compat] std::cout << rang::style::bold << rang::fg::red << "ERROR HERE! " ^ test.cpp:7:42: warning: enumeration type in nested name specifier is incompatible with C++98 [-Wc++98-compat] std::cout << rang::style::bold << rang::fg::red << "ERROR HERE! " ^ test.cpp:9:21: warning: enumeration type in nested name specifier is incompatible with C++98 [-Wc++98-compat] << rang::bg::red << rang::fg::gray << "ERROR INVERSE?" ^ test.cpp:9:38: warning: enumeration type in nested name specifier is incompatible with C++98 [-Wc++98-compat] << rang::bg::red << rang::fg::gray << "ERROR INVERSE?" ^ test.cpp:10:21: warning: enumeration type in nested name specifier is incompatible with C++98 [-Wc++98-compat] << rang::style::Reset << std::endl; ^ 15 warnings generated.
-Wc++98-compatif your code isn't fully backwards compatible? \$\endgroup\$-Weverythingin its full glory so that no warning gets away. \$\endgroup\$-Weverythingis meant for everyday use. I seem to have read somewhere that they added it so you could test all the warnings, then select the ones that make sense... \$\endgroup\$