use crate::io::reader::StreamEvent;
use crate::io::reader::line_batcher::{ReadResult, read_batch};
use anyhow::{Result, anyhow, ensure};
use shared_child::SharedChild;
use std::io::BufReader;
use std::process::ChildStdout;
use std::sync::Arc;
pub struct CommandReader {
reader: BufReader<ChildStdout>,
child: Arc<SharedChild>,
initial_read_complete_sent: bool,
}
impl CommandReader {
pub fn new(command: String) -> Result<CommandReader> {
spawn_command(command)
}
pub fn child(&self) -> Arc<SharedChild> {
self.child.clone()
}
pub fn next(&mut self) -> Result<StreamEvent> {
if !self.initial_read_complete_sent {
self.initial_read_complete_sent = true;
return Ok(StreamEvent::InitialReadComplete);
}
let event = match read_batch(&mut self.reader)? {
ReadResult::Eof => {
let status = self.child.wait()?;
ensure!(status.success(), "--exec command failed ({status})");
StreamEvent::Ended
}
ReadResult::Batch(batch) => StreamEvent::Lines(batch),
};
Ok(event)
}
}
#[cfg(not(windows))]
fn spawn_command(command: String) -> Result<CommandReader> {
use crate::io::reader::line_batcher::BUF_READER_CAPACITY;
use anyhow::Context;
use std::process::{Command, Stdio};
let trap_command = format!("trap '' INT; {command}");
let mut sh = Command::new("sh");
sh.arg("-c").arg(trap_command).stdout(Stdio::piped());
let child = SharedChild::spawn(&mut sh).context("Could not spawn process")?;
let stdout = child
.take_stdout()
.ok_or_else(|| anyhow!("Could not capture stdout of spawned process"))?;
let reader = BufReader::with_capacity(BUF_READER_CAPACITY, stdout);
Ok(CommandReader {
reader,
child: Arc::new(child),
initial_read_complete_sent: false,
})
}
#[cfg(windows)]
fn spawn_command(_command: String) -> Result<CommandReader> {
Err(anyhow!("The --exec flag is not supported on Windows"))
}
impl Drop for CommandReader {
fn drop(&mut self) {
let _ = self.child.kill();
}
}