use crate::writer::Writer;
use eyre::{Context, ContextCompat};
use std::process::{Child, ExitStatus};
use std::{
io::{BufRead, BufReader, Read, Write},
sync::{Arc, Mutex},
};
pub struct Process<'a> {
child: Child,
writer: &'a Writer,
stdout_lines: Arc<Mutex<Vec<String>>>,
stderr_lines: Arc<Mutex<Vec<String>>>,
}
impl<'a> Process<'a> {
pub fn new(child: Child, writer: &'a Writer) -> Self {
Process {
child,
writer,
stdout_lines: Arc::new(Mutex::new(Vec::new())),
stderr_lines: Arc::new(Mutex::new(Vec::new())),
}
}
fn thread(
&self,
reader: BufReader<impl Read + Send + 'static>,
lock: Arc<Mutex<Vec<String>>>,
) -> std::thread::JoinHandle<()> {
let is_plain_text = !self.writer.is_structured();
std::thread::spawn(move || {
for line in reader.lines().map_while(Result::ok) {
if let Ok(mut lines) = lock.lock() {
lines.push(line.clone());
}
if is_plain_text {
print!("\r\x1B[K");
}
let line_trimmed = if line.trim().len() > 48 {
format!("{}...", line.trim().chars().take(45).collect::<String>())
} else {
line.trim().to_string()
};
if is_plain_text {
print!("{}", console::style(&line_trimmed.trim()).dim());
}
let _ = std::io::stdout().flush();
}
})
}
pub fn log(&mut self) -> eyre::Result<ExitStatus> {
let stdout = self
.child
.stdout
.take()
.wrap_err("Failed to capture stdout")?;
let stderr = self
.child
.stderr
.take()
.wrap_err("Failed to capture stderr")?;
let stdout_reader = BufReader::new(stdout);
let stderr_reader = BufReader::new(stderr);
let stdout_lines_clone = Arc::clone(&self.stdout_lines);
let stderr_lines_clone = Arc::clone(&self.stderr_lines);
let stdout_thread = self.thread(stdout_reader, stdout_lines_clone);
let stderr_thread = self.thread(stderr_reader, stderr_lines_clone);
let status = self.child.wait().wrap_err("Command failed to complete")?;
stdout_thread.join().unwrap();
stderr_thread.join().unwrap();
if !self.writer.is_structured() {
print!("\r\x1B[K");
}
Ok(status)
}
pub fn print_error(&self) -> eyre::Result<()> {
if let Ok(lines) = self.stderr_lines.lock() {
self.writer.text(&format!(
"\n{}\n{}",
console::style("Error:").red().bold(),
lines.join("\n")
))?
}
Ok(())
}
pub fn print(&self) -> eyre::Result<()> {
if let Ok(lines) = self.stdout_lines.lock() {
self.writer.text(&format!("{}", lines.join("\n")))?;
}
Ok(())
}
pub fn output(&self) -> String {
if let Ok(lines) = self.stdout_lines.lock() {
return lines.join("\n");
}
"".into()
}
pub fn errors_output(&self) -> String {
if let Ok(lines) = self.stderr_lines.lock() {
return lines.join("\n");
}
"".into()
}
}