use tokio::io::AsyncBufReadExt; use tokio::io::BufReader; use tokio::io::Lines; use tokio::process::Child; use tokio::process::ChildStderr; use tokio::process::ChildStdout; use crate::Result; pub(crate) struct OutputStream { stdout: Option>>, stderr: Option>>, } impl OutputStream { pub(crate) fn from_child(child: &mut Child) -> Result { let stdout = child .stdout .take() .expect("child did not have a handle to stdout"); let stderr = child .stderr .take() .expect("child did not have a handle to stderr"); let stdout = BufReader::new(stdout).lines(); let stderr = BufReader::new(stderr).lines(); Ok(OutputStream { stdout: Some(stdout), stderr: Some(stderr), }) } pub(crate) async fn next_line(&mut self) -> Result { loop { match (&mut self.stdout, &mut self.stderr) { (None, None) => { return Ok(OutputLine::Done); } (None, Some(err)) => { if let Some(line) = err.next_line().await? { return Ok(OutputLine::Stderr(line)); } else { return Ok(OutputLine::Done); } } (Some(out), None) => { if let Some(line) = out.next_line().await? { return Ok(OutputLine::Stdout(line)); } else { return Ok(OutputLine::Done); } } (Some(out), Some(err)) => { tokio::select! { Ok(line) = out.next_line() => match line { Some(line) => { return Ok(OutputLine::Stdout(line)); }, None => { self.stdout.take(); }, }, Ok(line) = err.next_line() => match line { Some(line) => { return Ok(OutputLine::Stderr(line)); }, None => { self.stderr.take(); }, }, else => { return Ok(OutputLine::Done); }, }; } }; } } } pub(crate) enum OutputLine { Stdout(String), Stderr(String), Done, }