Piping stderr strips formatting/colors

When I grab stderr using Stdio::piped(), I am able to get the contents of stderr, but any formatting is lost. I've attempted this using both a String and using a Vec<u8> and in both occasions, the formatting is stripped and plain text is returned to the terminal.

Rough version of the code. Full code available at command.rs

fn do_something(mut cmd: Command) {
  let mut cmd = cmd.stdin(Stdio::inherit())
                   .stdout(Stdio::inherit())
                   .stderr(Stdio::piped())
                   .spawn()
                   .unwrap();
  let mut buffered_stderr = BufReader::new(cmd.stderr.take().unwrap());
  let status = cmd.wait();

  match status {
    Err(e) => whatever(e),
    Ok(status) => {
      let stderr = io::stderr();
      let mut handle = stderr.lock();
      
      while buffered_stderr.read_line(&mut buffer).unwrap() > 0 {
        let b = buffer.to_owned();
        buffer.clear();
        let _ = handle.write(b.as_bytes());
        // more stuff here
      }
    }
  }
}

Is there a way to maintain coloring in stderr?

Edit including a screenshot so we can stop talking about --color always

That's a feature of the command you are calling, as far as I know, so you'll probably have to convince the command that stderr is a terminal, and not actually piped. I don't know how that would be done, though.

Bah! That was my fear. cmd is calling out to rustc. The goal is to collect the errors thrown during compilation if the user has enabled telemetry.

Many commands allow forcing colors. In particular rustc --color always

While that is true, that doesn't fix the stderr pipe stripping the colors.

After fixing the broken example I can see colors just fine with --color always. (On Fedora Linux)

use std::process::{Command, Stdio};
use std::io::BufReader;
use std::io::prelude::*;
use std::io;

fn do_something(mut cmd: Command) {
  let mut cmd = cmd.stdin(Stdio::inherit())
                   .stdout(Stdio::inherit())
                   .stderr(Stdio::piped())
                   .spawn()
                   .unwrap();
  let mut buffered_stderr = BufReader::new(cmd.stderr.take().unwrap());
  let status = cmd.wait();

  match status {
    Err(_) => panic!(),
    Ok(_) => {
      let stderr = io::stderr();
      let mut handle = stderr.lock();
      let mut buffer = String::new();
      
      while buffered_stderr.read_line(&mut buffer).unwrap() > 0 {
        let b = buffer.to_owned();
        buffer.clear();
        let _ = handle.write(b.as_bytes());
        // more stuff here
      }
    }
  }
}

fn main() {
    let mut cmd = Command::new("rustc");
    cmd.arg("--color").arg("always").arg("qwe.rs");
    do_something(cmd);
}

I can see colors just fine with --color always. (On Fedora Linux)

Thanks for checking. I'm only running into this on Windows, so it's looking more like a platform specific issue.

On Windows (at least prior to 10) colors can only be controlled out-of-band so there's nothing you can do. See also Introducing Colored: the most simple way to add colors in your term! - #44 by retep998

Oh, very interesting. I'm not super worried about Windows 7 since it went out of mainstream support 3 months ago. Windows 8 is more worrying.

Thanks for that link, I'll dig into what's going on and see if we can get things working on Windows 10.

A slight point of clarification: the pipe itself is not stripping the colors; the program you are invoking is detecting the pipe and choosing not to send colors. (On older versions of Windows there was no way to send color information on a pipe; Unix supports it but "programs that want to use color should check for pipes first" has been a cultural norm since forever).