use std::ffi::OsStr;
use std::fs::File;
use std::io;
use std::path::Path;
use std::process::{Child, Command, Stdio};
pub struct ToolRead(Option<Child>);
impl io::Read for ToolRead {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let child = self
.0
.as_mut()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "external tool already closed".to_owned()))?;
let n = child.stdout.as_mut().unwrap().read(buf)?;
if n == 0 {
let output = self.0.take().unwrap().wait_with_output()?;
if !output.status.success() {
let mut errmsg = String::from_utf8_lossy(&output.stderr).to_string();
errmsg.truncate(errmsg.trim_end().len());
return Err(io::Error::new(io::ErrorKind::Other, errmsg));
}
}
Ok(n)
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
let mut child = self
.0
.take()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "external tool already closed".to_owned()))?;
let n = child.stdout.as_mut().unwrap().read_to_end(buf)?;
let output = child.wait_with_output()?;
if !output.status.success() {
let mut errmsg = String::from_utf8_lossy(&output.stderr).to_string();
errmsg.truncate(errmsg.trim_end().len());
return Err(io::Error::new(io::ErrorKind::Other, errmsg));
}
Ok(n)
}
}
impl Drop for ToolRead {
fn drop(&mut self) {
if let Some(mut child) = self.0.take() {
if let Err(_) | Ok(None) = child.try_wait() {
let _ = child.kill(); child.wait().unwrap();
}
}
}
}
impl ToolRead {
pub fn new<I, S>(cmd: &str, args: I) -> io::Result<ToolRead>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Ok(ToolRead(Some(
Command::new(cmd)
.env_clear()
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?,
)))
}
pub fn new_with_file<I, S>(cmd: &str, args: I, path: impl AsRef<Path>) -> io::Result<ToolRead>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Ok(ToolRead(Some(
Command::new(cmd)
.env_clear()
.args(args)
.stdin(File::open(path)?)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?,
)))
}
}
pub struct ToolWrite(Child);
impl io::Write for ToolWrite {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.stdin.as_mut().unwrap().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.stdin.as_mut().unwrap().flush()
}
}
impl Drop for ToolWrite {
fn drop(&mut self) {
self.0.wait().unwrap();
}
}
impl ToolWrite {
pub fn new<I, S>(cmd: &str, args: I) -> io::Result<ToolWrite>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Ok(ToolWrite(
Command::new(cmd)
.env_clear()
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.spawn()?,
))
}
pub fn new_with_file<I, S>(cmd: &str, args: I, path: impl AsRef<Path>) -> io::Result<ToolWrite>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Ok(ToolWrite(
Command::new(cmd)
.env_clear()
.args(args)
.stdin(Stdio::piped())
.stdout(File::create(path)?)
.spawn()?,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
use std::io::{Read, Write};
use tempfile::NamedTempFile;
#[test]
fn test_tool_read() -> Result<(), Box<dyn Error>> {
let mut input = ToolRead::new("/usr/bin/echo", ["Hallo Welt"])?;
let mut s = String::new();
input.read_to_string(&mut s)?;
assert_eq!(s, "Hallo Welt\n");
Ok(())
}
#[test]
fn test_tool_read_with_file() -> Result<(), Box<dyn Error>> {
let mut f = NamedTempFile::new()?;
writeln!(f, "Hello world")?;
let mut input = ToolRead::new_with_file("/usr/bin/cat", &[] as &[&str], f.path().to_str().unwrap())?;
let mut s = String::new();
input.read_to_string(&mut s)?;
assert_eq!(s, "Hello world\n");
Ok(())
}
#[test]
fn test_tool_write() -> Result<(), Box<dyn Error>> {
let mut f = NamedTempFile::new()?;
{
let mut output = ToolWrite::new("/usr/bin/tee", [f.path().to_str().unwrap()])?;
writeln!(output, "Hello world!")?;
}
let mut s = String::new();
f.read_to_string(&mut s)?;
assert_eq!(s, "Hello world!\n");
Ok(())
}
#[test]
fn test_tool_write_file() -> Result<(), Box<dyn Error>> {
let mut f = NamedTempFile::new()?;
{
let mut output = ToolWrite::new_with_file("/usr/bin/tac", &[] as &[&str], f.path().to_str().unwrap())?;
writeln!(output, "Welt")?;
writeln!(output, "Hallo")?;
}
let mut s = String::new();
f.read_to_string(&mut s)?;
assert_eq!(s, "Hallo\nWelt\n");
Ok(())
}
}