This is follow up to my question posted on codereview - Colorful output on terminal where I was trying to output coloured strings on terminal and detect it via isatty() call. However as @Jerry Coffin pointed out -
You use isatty to check whether standard output is connected to a terminal, regardless of what stream you're writing to. This means the rest of the functions only work correctly if you pass
std::coutas the stream to which they're going to write. Otherwise, you may allow formatting when writing to something that's not a TTY, and you may prohibit formatting when writing to something that is a TTY.
this was something that I wasn't aware of (read as had no experience in) and I wasn't even aware of the fact that cin/cout can be redirected elsewhere. So I tried to read more about it and found some existing questions on SO too. Here's what I've hacked together :
// initialize them at start of program - mandatory
std::streambuf const *coutbuf = std::cout.rdbuf();
std::streambuf const *cerrbuf = std::cerr.rdbuf();
std::streambuf const *clogbuf = std::clog.rdbuf();
// ignore this, just checks for TERM env var
inline 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;
}
rightTerm = supportsColor();
// would make necessary checks to ensure in terminal
inline bool isTerminal(const std::streambuf *osbuf)
{
FILE *currentStream = nullptr;
if(osbuf == coutbuf) {
currentStream = stdout;
}
else if(osbuf == cerrbuf || osbuf == clogbuf) {
currentStream = stderr;
}
else {
return false;
}
return isatty(fileno(currentStream));
}
// this would print
inline std::ostream &operator<<(std::ostream &os, rang::style v)
{
std::streambuf const *osbuf = os.rdbuf();
return rightTerm && isTerminal(osbuf)
? os << "\e[" << static_cast<int>(v) << "m"
: os;
}
My main issue is, although I've tested this manually, I'm not aware of the cases this might fail or bugs it might contain. Is this the right way to do this thing? Is there anything I might be missing?
Here are full files in case anyone wants to test -
rang.h
#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 RANG_rightTerm = false;
std::streambuf const *RANG_coutbuf = nullptr;
std::streambuf const *RANG_cerrbuf = nullptr;
std::streambuf const *RANG_clogbuf = nullptr;
inline 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;
}
namespace init {
inline void rang()
{
RANG_coutbuf = std::cout.rdbuf();
RANG_cerrbuf = std::cerr.rdbuf();
RANG_clogbuf = std::clog.rdbuf();
RANG_rightTerm = supportsColor();
}
}
inline bool isTerminal(const std::streambuf *osbuf)
{
FILE *currentStream = nullptr;
if(osbuf == RANG_coutbuf) {
currentStream = stdout;
}
else if(osbuf == RANG_cerrbuf || osbuf == RANG_clogbuf) {
currentStream = stderr;
}
else {
return false;
}
return isatty(fileno(currentStream));
}
inline std::ostream &operator<<(std::ostream &os, rang::style v)
{
std::streambuf const *osbuf = os.rdbuf();
return RANG_rightTerm && isTerminal(osbuf)
? os << "\e[" << static_cast<int>(v) << "m"
: os;
}
inline std::ostream &operator<<(std::ostream &os, rang::fg v)
{
std::streambuf const *osbuf = os.rdbuf();
return RANG_rightTerm && isTerminal(osbuf)
? os << "\e[" << static_cast<int>(v) << "m"
: os;
}
inline std::ostream &operator<<(std::ostream &os, rang::bg v)
{
std::streambuf const *osbuf = os.rdbuf();
return RANG_rightTerm && isTerminal(osbuf)
? os << "\e[" << static_cast<int>(v) << "m"
: os;
}
}
#endif /* ifndef RANG_H*/
test.cpp
#include <iostream>
#include <fstream>
#include <string>
#include "rang.h"
void f();
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;
std::ifstream in("in.txt");
std::streambuf *cinbuf = std::cin.rdbuf(); // save old buf
std::cin.rdbuf(in.rdbuf()); // redirect std::cin to in.txt!
std::ofstream out("out.txt");
std::streambuf *coutbuf = std::cout.rdbuf(); // save old buf
std::cout.rdbuf(out.rdbuf()); // redirect std::cout to out.txt!
std::string word;
std::cin >> word; // input from the file in.txt
std::cout << word << " "; // output to the file out.txt
f(); // call function
std::cin.rdbuf(cinbuf); // reset to standard input again
std::cout.rdbuf(coutbuf); // reset to standard output again
std::cin >> word; // input from the standard input
std::cout << word; // output to the standard input
return 0;
}
void f()
{
std::string line;
while(std::getline(std::cin, line)) // input from the file in.txt
{
std::cout << line << "\n"; // output to the file out.txt
}
}
in.txt
Lalala1
Lalala2
Lalala3
command would be : g++ test.cpp -std=c++11
I've also tagged c language although this is c++ code because the relevant code is shared b/w two and I don't want to miss any suggestions
Aucun commentaire:
Enregistrer un commentaire