#![deny(missing_docs)]
#![allow(stable_features)]
#![feature(conservative_impl_trait)]
extern crate mio;
extern crate mio_extras;
use mio::{Evented, Poll, PollOpt, Ready, Token};
use mio_extras::channel::{channel, Receiver, Sender};
use std::io::{Error, ErrorKind, Read, Result, Write};
use std::process::{Child, Command, ExitStatus};
use std::thread::spawn;
#[cfg(test)]
mod test;
pub trait CommandAsync {
fn spawn_async(&mut self) -> Result<Process>;
}
impl CommandAsync for Command {
fn spawn_async(&mut self) -> Result<Process> {
let child = self.spawn()?;
Ok(Process::from_child(child))
}
}
pub struct Process {
receiver: Receiver<ProcessEvent>,
stdin: Option<std::process::ChildStdin>,
}
impl Process {
pub fn try_recv(&mut self) -> std::result::Result<ProcessEvent, std::sync::mpsc::TryRecvError> {
self.receiver.try_recv()
}
pub fn from_child(mut child: Child) -> Process {
let (sender, receiver) = channel();
if let Some(stdout) = child.stdout.take() {
spawn(create_reader(stdout, sender.clone(), StdioChannel::Stdout));
}
if let Some(stderr) = child.stderr.take() {
spawn(create_reader(stderr, sender.clone(), StdioChannel::Stderr));
}
let stdin = child.stdin.take();
spawn(move || {
let result = match child.wait_with_output() {
Err(e) => {
let _ = sender.send(ProcessEvent::CommandError(e));
return;
}
Ok(r) => r,
};
if !result.stdout.is_empty() {
if SendResult::Abort
== try_send_buffer(&result.stdout, StdioChannel::Stdout, &sender)
{
return;
}
}
if !result.stderr.is_empty() {
if SendResult::Abort
== try_send_buffer(&result.stderr, StdioChannel::Stderr, &sender)
{
return;
}
}
let _ = sender.send(ProcessEvent::Exit(result.status));
});
Process { receiver, stdin }
}
}
impl Write for Process {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
match self.stdin.as_mut() {
Some(ref mut stdin) => stdin.write(buf),
None => Err(Error::from(ErrorKind::NotConnected)),
}
}
fn flush(&mut self) -> Result<()> {
match self.stdin.as_mut() {
Some(ref mut stdin) => stdin.flush(),
None => Err(Error::from(ErrorKind::NotConnected)),
}
}
}
impl Evented for Process {
fn register(&self, poll: &Poll, token: Token, interest: Ready, ops: PollOpt) -> Result<()> {
self.receiver.register(poll, token, interest, ops)
}
fn reregister(&self, poll: &Poll, token: Token, interest: Ready, ops: PollOpt) -> Result<()> {
self.receiver.reregister(poll, token, interest, ops)
}
fn deregister(&self, poll: &Poll) -> Result<()> {
self.receiver.deregister(poll)
}
}
#[derive(PartialEq, Eq, Debug)]
enum SendResult {
Abort,
Ok,
}
fn try_send_buffer(
buffer: &[u8],
channel: StdioChannel,
sender: &Sender<ProcessEvent>,
) -> SendResult {
let str = match std::str::from_utf8(buffer) {
Ok(s) => s,
Err(e) => {
let _ = sender.send(ProcessEvent::Utf8Error(channel, e));
return SendResult::Abort;
}
};
if str.is_empty() {
println!("Aborting try_send_buffer because we're sending empty strings");
println!("Channel: {:?}", channel);
return SendResult::Abort;
}
if let Err(_) = sender.send(ProcessEvent::Data(channel, String::from(str))) {
SendResult::Abort
} else {
SendResult::Ok
}
}
fn create_reader<T: Read + 'static>(
mut stream: T,
sender: Sender<ProcessEvent>,
channel: StdioChannel,
) -> impl FnOnce() {
move || {
let mut buffer = [0u8; 1024];
loop {
match stream.read(&mut buffer[..]) {
Ok(0) => {
break;
}
Ok(n) => {
if SendResult::Abort == try_send_buffer(&buffer[..n], channel, &sender) {
break;
}
}
Err(e) => {
let _ = sender.send(ProcessEvent::IoError(channel, e));
break;
}
}
}
}
}
#[derive(Debug)]
pub enum ProcessEvent {
Data(StdioChannel, String),
CommandError(std::io::Error),
IoError(StdioChannel, std::io::Error),
Utf8Error(StdioChannel, std::str::Utf8Error),
Exit(ExitStatus),
}
#[derive(Copy, Clone, Debug)]
pub enum StdioChannel {
Stdout,
Stderr,
}