1use anyhow::Result;
2use std::env;
3use std::io::{self, BufRead, IsTerminal, Read};
4use std::sync::mpsc::{self, Sender};
5use std::thread;
6
7pub fn should_use_color() -> bool {
8 if env::var_os("NO_COLOR").is_some() {
9 return false;
10 }
11
12 if env::var_os("CLICOLOR_FORCE").is_some_and(|v| v != "0") {
13 return true;
14 }
15
16 match env::var("OPAL_COLOR") {
17 Ok(val) if matches!(val.as_str(), "always" | "1" | "true") => return true,
18 Ok(val) if matches!(val.as_str(), "never" | "0" | "false") => return false,
19 _ => {}
20 }
21
22 if !io::stdout().is_terminal() {
23 return false;
24 }
25
26 true
27}
28
29pub fn stream_lines<F>(
30 stdout: impl Read + Send + 'static,
31 stderr: impl Read + Send + 'static,
32 mut on_line: F,
33) -> Result<()>
34where
35 F: FnMut(String) -> Result<()>,
36{
37 let (tx, rx) = mpsc::channel::<Result<String, io::Error>>();
38 spawn_reader(stdout, tx.clone());
39 spawn_reader(stderr, tx.clone());
40 drop(tx);
41
42 for line in rx {
43 let line = line?;
44 on_line(line)?;
45 }
46
47 Ok(())
48}
49
50fn spawn_reader<R>(reader: R, tx: Sender<Result<String, io::Error>>)
51where
52 R: Read + Send + 'static,
53{
54 thread::spawn(move || {
55 let mut reader = io::BufReader::new(reader);
56 loop {
57 let mut buf = String::new();
58 match reader.read_line(&mut buf) {
59 Ok(0) => break,
60 Ok(_) => {
61 if buf.ends_with('\n') {
62 buf.pop();
63 if buf.ends_with('\r') {
64 buf.pop();
65 }
66 }
67 if tx.send(Ok(buf)).is_err() {
68 break;
69 }
70 }
71 Err(err) => {
72 let _ = tx.send(Err(err));
73 break;
74 }
75 }
76 }
77 });
78}