aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2019-10-24 12:51:04 -0400
committerJesse Luehrs <doy@tozt.net>2019-10-24 12:51:04 -0400
commit24cc5cac002f87ed501fddb8e48a2315288fdb1f (patch)
tree745eeb10158c1daab5bdb93c15dc6d8c4a89694c /src
parentec5ade52abe906b02871fd7ee72593d259f77dec (diff)
downloadtokio-pty-process-stream-24cc5cac002f87ed501fddb8e48a2315288fdb1f.tar.gz
tokio-pty-process-stream-24cc5cac002f87ed501fddb8e48a2315288fdb1f.zip
docs
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs176
1 files changed, 176 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 406e54b..2c5aafb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,143 @@
+//! This crate wraps `tokio-pty-process` in order to provide a simpler API as
+//! a single stream object.
+//!
+//! # Overview
+//!
+//! When you need to interact with an interactive program as part of an
+//! asynchronous application, it can be tricky to figure out the way to
+//! structure the different parts that are required. This crate simplifies the
+//! API down to just providing the input via an `AsyncRead` object, and then
+//! getting updates about what the program is doing via results generated by a
+//! stream.
+//!
+//! # Synopsis
+//!
+//! This is an example of how to run an interactive program and have it behave
+//! identically to running it in the shell. Note that we have to use our own
+//! `Stdin` implementation here because `tokio::io::stdin()` is actually
+//! blocking, and so polling it as part of an interactive application doesn't
+//! work correctly. The implementation of `Stdin` is elided here, but you can
+//! see the full implementation in `examples/shell.rs` in the repository.
+//!
+//! ```no_run
+//! # use futures::future::Future as _;
+//! # use futures::stream::Stream as _;
+//! # use std::io::{Read as _, Write as _};
+//! #
+//! let mut argv = std::env::args();
+//! argv.next().unwrap();
+//! let cmd = argv.next().unwrap();
+//! let args: Vec<_> = argv.collect();
+//!
+//! let process =
+//! tokio_pty_process_stream::Process::new(&cmd, &args, Stdin::new());
+//!
+//! let _raw = crossterm::RawScreen::into_raw_mode().unwrap();
+//! tokio::run(
+//! process
+//! .for_each(|ev| {
+//! match ev {
+//! tokio_pty_process_stream::Event::CommandStart {
+//! ..
+//! } => {}
+//! tokio_pty_process_stream::Event::Output { data } => {
+//! let stdout = std::io::stdout();
+//! let mut stdout = stdout.lock();
+//! stdout.write_all(&data).unwrap();
+//! stdout.flush().unwrap();
+//! }
+//! tokio_pty_process_stream::Event::CommandExit {
+//! ..
+//! } => {}
+//! }
+//! futures::future::ok(())
+//! })
+//! .map_err(|e| panic!(e)),
+//! );
+//! #
+//! # struct EventedStdin;
+//! #
+//! # const STDIN: i32 = 0;
+//! #
+//! # impl std::io::Read for EventedStdin {
+//! # fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+//! # let stdin = std::io::stdin();
+//! # let mut stdin = stdin.lock();
+//! # stdin.read(buf)
+//! # }
+//! # }
+//! #
+//! # impl mio::Evented for EventedStdin {
+//! # fn register(
+//! # &self,
+//! # poll: &mio::Poll,
+//! # token: mio::Token,
+//! # interest: mio::Ready,
+//! # opts: mio::PollOpt,
+//! # ) -> std::io::Result<()> {
+//! # let fd = STDIN as std::os::unix::io::RawFd;
+//! # let eventedfd = mio::unix::EventedFd(&fd);
+//! # eventedfd.register(poll, token, interest, opts)
+//! # }
+//! #
+//! # fn reregister(
+//! # &self,
+//! # poll: &mio::Poll,
+//! # token: mio::Token,
+//! # interest: mio::Ready,
+//! # opts: mio::PollOpt,
+//! # ) -> std::io::Result<()> {
+//! # let fd = STDIN as std::os::unix::io::RawFd;
+//! # let eventedfd = mio::unix::EventedFd(&fd);
+//! # eventedfd.reregister(poll, token, interest, opts)
+//! # }
+//! #
+//! # fn deregister(&self, poll: &mio::Poll) -> std::io::Result<()> {
+//! # let fd = STDIN as std::os::unix::io::RawFd;
+//! # let eventedfd = mio::unix::EventedFd(&fd);
+//! # eventedfd.deregister(poll)
+//! # }
+//! # }
+//! #
+//! # pub struct Stdin {
+//! # input: tokio::reactor::PollEvented2<EventedStdin>,
+//! # }
+//! #
+//! # impl Stdin {
+//! # pub fn new() -> Self {
+//! # Default::default()
+//! # }
+//! # }
+//! #
+//! # impl Default for Stdin {
+//! # fn default() -> Self {
+//! # Self {
+//! # input: tokio::reactor::PollEvented2::new(EventedStdin),
+//! # }
+//! # }
+//! # }
+//! #
+//! # impl std::io::Read for Stdin {
+//! # fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+//! # self.input.read(buf)
+//! # }
+//! # }
+//! #
+//! # impl tokio::io::AsyncRead for Stdin {
+//! # fn poll_read(
+//! # &mut self,
+//! # buf: &mut [u8],
+//! # ) -> std::result::Result<futures::Async<usize>, tokio::io::Error> {
+//! # let ready = mio::Ready::readable();
+//! # futures::try_ready!(self.input.poll_read_ready(ready));
+//! #
+//! # let res = self.read(buf)?;
+//! # self.input.clear_read_ready(ready)?;
+//! # Ok(futures::Async::Ready(res))
+//! # }
+//! # }
+//! ```
+
#![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
#![allow(clippy::missing_const_for_fn)]
@@ -11,34 +151,51 @@ use tokio_pty_process::{CommandExt as _, PtyMaster as _};
const READ_BUFFER_SIZE: usize = 4 * 1024;
+/// Errors returned by the process stream.
#[derive(Debug, snafu::Snafu)]
pub enum Error {
+ /// failed to open a pty
#[snafu(display("failed to open a pty: {}", source))]
OpenPty { source: std::io::Error },
+ /// failed to poll for process exit
#[snafu(display("failed to poll for process exit: {}", source))]
ProcessExitPoll { source: std::io::Error },
+ /// failed to read from pty
#[snafu(display("failed to read from pty: {}", source))]
ReadPty { source: std::io::Error },
+ /// failed to read from terminal
#[snafu(display("failed to read from terminal: {}", source))]
ReadTerminal { source: std::io::Error },
+ /// failed to resize pty
#[snafu(display("failed to resize pty: {}", source))]
ResizePty { source: std::io::Error },
+ /// failed to spawn process
#[snafu(display("failed to spawn process for `{}`: {}", cmd, source))]
SpawnProcess { cmd: String, source: std::io::Error },
+ /// failed to write to pty
#[snafu(display("failed to write to pty: {}", source))]
WritePty { source: std::io::Error },
}
+/// Represents events generated by the process.
#[derive(Debug, PartialEq, Eq)]
pub enum Event {
+ /// Emitted once the command has been successfully spawned.
CommandStart { cmd: String, args: Vec<String> },
+
+ /// Emitted every time the command produces output. Note that when a
+ /// process is running under a pty, both stdout and stderr are attached to
+ /// the single pty input - there is no way to differentiate them when
+ /// reading from the pty output.
Output { data: Vec<u8> },
+
+ /// Emitted when the command has exited.
CommandExit { status: std::process::ExitStatus },
}
@@ -68,6 +225,13 @@ impl State {
}
}
+/// A spawned process.
+///
+/// Wraps `AsyncPtyMaster` and `Child` from `tokio-pty-process` to provide a
+/// view of the process as a single stream which emits events. In particular,
+/// the stream will return an event when the process starts, when it writes
+/// output to the pty, and when it exits. See the `Event` type for more
+/// details.
pub struct Process<R: tokio::io::AsyncRead> {
state: State,
input: R,
@@ -83,6 +247,15 @@ pub struct Process<R: tokio::io::AsyncRead> {
}
impl<R: tokio::io::AsyncRead + 'static> Process<R> {
+ /// Creates a new process stream.
+ ///
+ /// The process is not spawned and the pty is not opened until `poll` is
+ /// called.
+ ///
+ /// Takes as input the command and arguments to run, as well as the
+ /// `AsyncRead` object to read input from. Typically you will pass in
+ /// something connected to stdin here, although other options may be more
+ /// useful for automation or testing.
pub fn new(cmd: &str, args: &[String], input: R) -> Self {
Self {
state: State::new(),
@@ -99,6 +272,9 @@ impl<R: tokio::io::AsyncRead + 'static> Process<R> {
}
}
+ /// Requests a change to the pty's terminal size.
+ ///
+ /// This will only be applied on the next call to `poll`.
pub fn resize(&mut self, rows: u16, cols: u16) {
self.needs_resize = Some((rows, cols));
}