86 lines
2.7 KiB
Rust
86 lines
2.7 KiB
Rust
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<Lines<BufReader<ChildStdout>>>,
|
|
stderr: Option<Lines<BufReader<ChildStderr>>>,
|
|
}
|
|
|
|
impl OutputStream {
|
|
pub(crate) fn from_child(child: &mut Child) -> Result<Self> {
|
|
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<OutputLine> {
|
|
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,
|
|
}
|