From 244d08b3409f8d44b6fd0cc55772e702b7df1014 Mon Sep 17 00:00:00 2001 From: LightVillet Date: Sat, 14 Mar 2026 19:32:10 +0200 Subject: [PATCH 1/3] feat: Add `Builder` for process --- src/lib.rs | 3 +- src/session.rs | 93 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 76 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cc656a03..4be638f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,7 +74,8 @@ pub mod reader; pub mod session; pub use reader::ReadUntil; -pub use session::{spawn, spawn_bash, spawn_python, spawn_stream, spawn_with_options}; +pub use session::Builder; +pub use session::{spawn, spawn_bash, spawn_python, spawn_stream}; // include the README.md here to test its doc #[doc = include_str!("../README.md")] diff --git a/src/session.rs b/src/session.rs index 07399812..abd93b6b 100644 --- a/src/session.rs +++ b/src/session.rs @@ -199,6 +199,76 @@ impl PtySession { } } +/// Process factory, which can be used in order to configure the properties of a command. +#[derive(Default)] +pub struct Builder { + /// A command to spawn + pub(super) command: Option, + /// If Some: all `exp_*` commands time out after `timeout`, if None: never times out. + /// By default timeout is set to 30s + pub(super) timeout_ms: Option, + /// Whether to filter out escape codes, such as colors. + pub(super) strip_ansi_escape_codes: bool, +} + +impl Builder { + pub fn new(command: Command) -> Self { + Self { + command: Some(command), + timeout_ms: Some(30), + ..Default::default() + } + } + + /// Set the command which will be executed + pub fn command(mut self, command: Command) -> Self { + self.command = Some(command); + self + } + + /// Set the timeout for the command. + /// + /// If Some: all `exp_*` commands time out after `timeout`, if None: never times out. + /// It's highly recommended to put a timeout there, as otherwise in case of + /// a problem the program just hangs instead of exiting with an + /// error message indicating where it stopped. + /// For automation 30s (the default in pexpect) is a good value. + pub fn timeout(mut self, timeout_ms: Option) -> Self { + self.timeout_ms = timeout_ms; + self + } + + /// Set filtering out escape codes, such as colors. + pub fn strip_ansi_escape_codes(mut self) -> Self { + self.strip_ansi_escape_codes = true; + self + } + + pub fn spawn(self) -> Result { + let command = self.command.ok_or(Error::EmptyProgramName)?; + + #[cfg(feature = "which")] + { + let _ = which::which(command.get_program())?; + } + let mut process = PtyProcess::new(command)?; + process.set_kill_timeout(self.timeout_ms); + + let Self { + timeout_ms, + strip_ansi_escape_codes, + .. + } = self; + + let options = Options { + timeout_ms, + strip_ansi_escape_codes, + }; + + PtySession::new(process, options) + } +} + /// Turn e.g. "prog arg1 arg2" into ["prog", "arg1", "arg2"] /// Also takes care of single and double quotes fn tokenize_command(program: &str) -> Result, Error> { @@ -232,25 +302,10 @@ pub fn spawn(program: &str, timeout_ms: Option) -> Result) -> Result { - spawn_with_options( - command, - Options { - timeout_ms, - strip_ansi_escape_codes: false, - }, - ) -} - -/// See `spawn` -pub fn spawn_with_options(command: Command, options: Options) -> Result { - #[cfg(feature = "which")] - { - let _ = which::which(command.get_program())?; - } - let mut process = PtyProcess::new(command)?; - process.set_kill_timeout(options.timeout_ms); - - PtySession::new(process, options) + let builder = Builder::new(command); + builder + .timeout(timeout_ms) + .spawn() } /// A repl session: e.g. bash or the python shell: From 4e4b78c048ad9aef2a22f65ad9f86be70f36c205 Mon Sep 17 00:00:00 2001 From: LightVillet Date: Sat, 14 Mar 2026 22:00:00 +0200 Subject: [PATCH 2/3] chore!: `timeout` now has type `std::time::Duration` --- README.md | 9 ++++-- examples/bash.rs | 3 +- examples/bash_read.rs | 4 ++- examples/exit_code.rs | 8 +++-- examples/ftp.rs | 6 +++- examples/repl.rs | 3 +- examples/tcp.rs | 3 +- src/lib.rs | 6 ++-- src/process.rs | 4 +-- src/reader.rs | 8 ++--- src/session.rs | 69 ++++++++++++++++++++++++------------------- 11 files changed, 74 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 95451423..d0837051 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,10 @@ Simple example for interacting via ftp: ```rust,no_run use rexpect::spawn; use rexpect::error::*; +use std::time; fn do_ftp() -> Result<(), Error> { - let mut p = spawn("ftp speedtest.tele2.net", Some(30_000))?; + let mut p = spawn("ftp speedtest.tele2.net", Some(time::Duration::from_secs(30)))?; p.exp_regex("Name \\(.*\\):")?; p.send_line("anonymous")?; p.exp_string("Password")?; @@ -55,9 +56,10 @@ fn main() { ```rust,no_run use rexpect::spawn_bash; use rexpect::error::*; +use std::time; fn do_bash() -> Result<(), Error> { - let mut p = spawn_bash(Some(2000))?; + let mut p = spawn_bash(Some(time::Duration::from_secs(2)))?; // case 1: wait until program is done p.send_line("hostname")?; @@ -105,9 +107,10 @@ goes into nirvana. There are two functions to ensure that: ```rust,no_run use rexpect::spawn_bash; use rexpect::error::*; +use std::time; fn do_bash_jobcontrol() -> Result<(), Error> { - let mut p = spawn_bash(Some(1000))?; + let mut p = spawn_bash(Some(time::Duration::from_secs(1)))?; p.execute("ping 8.8.8.8", "bytes of data")?; p.send_control('z')?; p.wait_for_prompt()?; diff --git a/examples/bash.rs b/examples/bash.rs index a95ac46b..95530823 100644 --- a/examples/bash.rs +++ b/examples/bash.rs @@ -1,8 +1,9 @@ use rexpect::error::Error; use rexpect::spawn_bash; +use std::time; fn main() -> Result<(), Error> { - let mut p = spawn_bash(Some(1000))?; + let mut p = spawn_bash(Some(time::Duration::from_secs(1)))?; p.execute("ping 8.8.8.8", "bytes")?; p.send_control('z')?; p.wait_for_prompt()?; diff --git a/examples/bash_read.rs b/examples/bash_read.rs index a5e37258..efbde1a0 100644 --- a/examples/bash_read.rs +++ b/examples/bash_read.rs @@ -1,8 +1,10 @@ +use std::time; + use rexpect::error::Error; use rexpect::spawn_bash; fn main() -> Result<(), Error> { - let mut p = spawn_bash(Some(2000))?; + let mut p = spawn_bash(Some(time::Duration::from_secs(2)))?; // case 1: wait until program is done p.send_line("hostname")?; diff --git a/examples/exit_code.rs b/examples/exit_code.rs index 67486de6..d9f81c3a 100644 --- a/examples/exit_code.rs +++ b/examples/exit_code.rs @@ -1,19 +1,23 @@ use rexpect::error::Error; use rexpect::process::wait; use rexpect::spawn; +use std::time; /// The following code emits: /// cat exited with code 0, all good! /// cat exited with code 1 /// Output (stdout and stderr): cat: /this/does/not/exist: No such file or directory fn main() -> Result<(), Error> { - let p = spawn("cat /etc/passwd", Some(2000))?; + let p = spawn("cat /etc/passwd", Some(time::Duration::from_secs(2)))?; match p.process.wait() { Ok(wait::WaitStatus::Exited(_, 0)) => println!("cat exited with code 0, all good!"), _ => println!("cat exited with code >0, or it was killed"), } - let mut p = spawn("cat /this/does/not/exist", Some(2000))?; + let mut p = spawn( + "cat /this/does/not/exist", + Some(time::Duration::from_secs(2)), + )?; match p.process.wait() { Ok(wait::WaitStatus::Exited(_, 0)) => println!("cat succeeded"), Ok(wait::WaitStatus::Exited(_, c)) => { diff --git a/examples/ftp.rs b/examples/ftp.rs index d1571879..27c56da8 100644 --- a/examples/ftp.rs +++ b/examples/ftp.rs @@ -1,8 +1,12 @@ use rexpect::error::Error; use rexpect::spawn; +use std::time; fn main() -> Result<(), Error> { - let mut p = spawn("ftp speedtest.tele2.net", Some(2000))?; + let mut p = spawn( + "ftp speedtest.tele2.net", + Some(time::Duration::from_secs(2)), + )?; p.exp_regex("Name \\(.*\\):")?; p.send_line("anonymous")?; p.exp_string("Password")?; diff --git a/examples/repl.rs b/examples/repl.rs index 4a305e46..4bf59d7b 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -3,6 +3,7 @@ use rexpect::error::Error; use rexpect::session::PtyReplSession; use rexpect::spawn; +use std::time; fn ed_session() -> Result { let mut ed = PtyReplSession { @@ -12,7 +13,7 @@ fn ed_session() -> Result { // used for `wait_for_prompt()` prompt: "> ".to_owned(), - pty_session: spawn("/bin/ed -p '> '", Some(2000))?, + pty_session: spawn("/bin/ed -p '> '", Some(time::Duration::from_secs(2)))?, // command which is sent when the instance of this struct is dropped // in the below example this is not needed, but if you don't explicitly // exit a REPL then rexpect tries to send a SIGTERM and depending on the repl diff --git a/examples/tcp.rs b/examples/tcp.rs index 5f3c11f4..9ed80890 100644 --- a/examples/tcp.rs +++ b/examples/tcp.rs @@ -1,11 +1,12 @@ use rexpect::spawn_stream; use std::error::Error; use std::net::TcpStream; +use std::time; fn main() -> Result<(), Box> { let tcp = TcpStream::connect("www.google.com:80")?; let tcp_w = tcp.try_clone()?; - let mut session = spawn_stream(tcp, tcp_w, Some(2000)); + let mut session = spawn_stream(tcp, tcp_w, Some(time::Duration::from_secs(2))); session.send_line("GET / HTTP/1.1")?; session.send_line("Host: www.google.com")?; session.send_line("Accept-Language: fr")?; diff --git a/src/lib.rs b/src/lib.rs index 4be638f3..49df311c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,9 +18,10 @@ //! //! use rexpect::spawn; //! use rexpect::error::Error; +//! use std::time; //! //! fn main() -> Result<(), Error> { -//! let mut p = spawn("ftp speedtest.tele2.net", Some(2000))?; +//! let mut p = spawn("ftp speedtest.tele2.net", Some(time::Duration::from_secs(2)))?; //! p.exp_regex("Name \\(.*\\):")?; //! p.send_line("anonymous")?; //! p.exp_string("Password")?; @@ -48,9 +49,10 @@ //! ```no_run //! use rexpect::spawn_bash; //! use rexpect::error::Error; +//! use std::time; //! //! fn main() -> Result<(), Error> { -//! let mut p = spawn_bash(Some(30_000))?; +//! let mut p = spawn_bash(Some(time::Duration::from_secs(30)))?; //! p.execute("ping 8.8.8.8", "bytes of data")?; //! p.send_control('z')?; //! p.wait_for_prompt()?; diff --git a/src/process.rs b/src/process.rs index db1c2507..4e7ee207 100644 --- a/src/process.rs +++ b/src/process.rs @@ -147,8 +147,8 @@ impl PtyProcess { /// At the drop of `PtyProcess` the running process is killed. This is blocking forever if /// the process does not react to a normal kill. If `kill_timeout` is set the process is /// `kill -9`ed after duration - pub fn set_kill_timeout(&mut self, timeout_ms: Option) { - self.kill_timeout = timeout_ms.map(time::Duration::from_millis); + pub fn set_kill_timeout(&mut self, timeout: Option) { + self.kill_timeout = timeout; } /// Get status of child process, non-blocking. diff --git a/src/reader.rs b/src/reader.rs index a6ae6e54..b8b47d64 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -106,7 +106,7 @@ pub fn find(needle: &ReadUntil, buffer: &str, eof: bool) -> Option<(usize, usize /// - `strip_ansi_escape_codes`: Whether to filter out escape codes, such as colors. #[derive(Default)] pub struct Options { - pub timeout_ms: Option, + pub timeout: Option, pub strip_ansi_escape_codes: bool, } @@ -173,7 +173,7 @@ impl NBReader { reader: rx, buffer: String::with_capacity(1024), eof: false, - timeout: options.timeout_ms.map(time::Duration::from_millis), + timeout: options.timeout, } } @@ -406,7 +406,7 @@ mod tests { let mut r = NBReader::new( f, Options { - timeout_ms: None, + timeout: None, strip_ansi_escape_codes: true, }, ); @@ -423,7 +423,7 @@ mod tests { let mut r = NBReader::new( f, Options { - timeout_ms: None, + timeout: None, strip_ansi_escape_codes: true, }, ); diff --git a/src/session.rs b/src/session.rs index abd93b6b..f716b852 100644 --- a/src/session.rs +++ b/src/session.rs @@ -9,6 +9,7 @@ use std::io::LineWriter; use std::io::prelude::*; use std::ops::{Deref, DerefMut}; use std::process::Command; +use std::time; use tempfile; pub struct StreamSession { @@ -135,10 +136,11 @@ impl StreamSession { /// ``` /// use rexpect::{spawn, ReadUntil}; /// # use rexpect::error::Error; + /// use std::time; /// /// # fn main() { /// # || -> Result<(), Error> { - /// let mut s = spawn("cat", Some(1000))?; + /// let mut s = spawn("cat", Some(time::Duration::from_secs(1)))?; /// s.send_line("hello, polly!")?; /// s.exp_any(vec![ReadUntil::String("hello".into()), /// ReadUntil::EOF])?; @@ -179,10 +181,11 @@ impl DerefMut for PtySession { /// /// use rexpect::spawn; /// # use rexpect::error::Error; +/// use std::time; /// /// # fn main() { /// # || -> Result<(), Error> { -/// let mut s = spawn("cat", Some(1000))?; +/// let mut s = spawn("cat", Some(time::Duration::from_secs(1)))?; /// s.send_line("hello, polly!")?; /// let line = s.read_line()?; /// assert_eq!("hello, polly!", line); @@ -206,7 +209,7 @@ pub struct Builder { pub(super) command: Option, /// If Some: all `exp_*` commands time out after `timeout`, if None: never times out. /// By default timeout is set to 30s - pub(super) timeout_ms: Option, + pub(super) timeout: Option, /// Whether to filter out escape codes, such as colors. pub(super) strip_ansi_escape_codes: bool, } @@ -215,7 +218,7 @@ impl Builder { pub fn new(command: Command) -> Self { Self { command: Some(command), - timeout_ms: Some(30), + timeout: Some(time::Duration::from_secs(30)), ..Default::default() } } @@ -233,8 +236,8 @@ impl Builder { /// a problem the program just hangs instead of exiting with an /// error message indicating where it stopped. /// For automation 30s (the default in pexpect) is a good value. - pub fn timeout(mut self, timeout_ms: Option) -> Self { - self.timeout_ms = timeout_ms; + pub fn timeout(mut self, timeout: Option) -> Self { + self.timeout = timeout; self } @@ -252,16 +255,16 @@ impl Builder { let _ = which::which(command.get_program())?; } let mut process = PtyProcess::new(command)?; - process.set_kill_timeout(self.timeout_ms); + process.set_kill_timeout(self.timeout); let Self { - timeout_ms, + timeout, strip_ansi_escape_codes, .. } = self; let options = Options { - timeout_ms, + timeout, strip_ansi_escape_codes, }; @@ -282,13 +285,13 @@ fn tokenize_command(program: &str) -> Result, Error> { /// /// - `program`: This is split at spaces and turned into a `process::Command` /// if you wish more control over this, use `spawn_command` -/// - `timeout`: If Some: all `exp_*` commands time out after x milliseconds, if None: never times +/// - `timeout`: If Some: all `exp_*` commands time out after `timeout`, if None: never times /// out. /// It's highly recommended to put a timeout there, as otherwise in case of /// a problem the program just hangs instead of exiting with an /// error message indicating where it stopped. /// For automation 30'000 (30s, the default in pexpect) is a good value. -pub fn spawn(program: &str, timeout_ms: Option) -> Result { +pub fn spawn(program: &str, timeout: Option) -> Result { if program.is_empty() { return Err(Error::EmptyProgramName); } @@ -297,15 +300,16 @@ pub fn spawn(program: &str, timeout_ms: Option) -> Result) -> Result { +pub fn spawn_command( + command: Command, + timeout: Option, +) -> Result { let builder = Builder::new(command); - builder - .timeout(timeout_ms) - .spawn() + builder.timeout(timeout).spawn() } /// A repl session: e.g. bash or the python shell: @@ -354,10 +358,11 @@ impl PtyReplSession { /// ``` /// use rexpect::spawn_bash; /// # use rexpect::error::Error; + /// use std::time; /// /// # fn main() { /// # || -> Result<(), Error> { - /// let mut p = spawn_bash(Some(1000))?; + /// let mut p = spawn_bash(Some(time::Duration::from_secs(1)))?; /// p.execute("cat <(echo ready) -", "ready")?; /// p.send_line("hans")?; /// p.exp_string("hans")?; @@ -436,7 +441,7 @@ impl Drop for PtyReplSession { /// Also: if you start a program you should use `execute` and not `send_line`. /// /// For an example see the README -pub fn spawn_bash(timeout: Option) -> Result { +pub fn spawn_bash(timeout: Option) -> Result { // unfortunately working with a temporary tmpfile is the only // way to guarantee that we are "in step" with the prompt // all other attempts were futile, especially since we cannot @@ -478,7 +483,7 @@ pub fn spawn_bash(timeout: Option) -> Result { /// Spawn the python shell /// /// This is just a proof of concept implementation (and serves for documentation purposes) -pub fn spawn_python(timeout: Option) -> Result { +pub fn spawn_python(timeout: Option) -> Result { spawn_command(Command::new("python"), timeout).map(|p| PtyReplSession { prompt: ">>> ".to_owned(), pty_session: p, @@ -491,13 +496,13 @@ pub fn spawn_python(timeout: Option) -> Result { pub fn spawn_stream( reader: R, writer: W, - timeout_ms: Option, + timeout: Option, ) -> StreamSession { StreamSession::new( reader, writer, Options { - timeout_ms, + timeout, strip_ansi_escape_codes: false, }, ) @@ -509,7 +514,7 @@ mod tests { #[test] fn test_read_line() -> Result<(), Error> { - let mut s = spawn("cat", Some(100000))?; + let mut s = spawn("cat", Some(time::Duration::from_secs(100)))?; s.send_line("hans")?; assert_eq!("hans", s.read_line()?); let should = crate::process::wait::WaitStatus::Signaled( @@ -523,7 +528,8 @@ mod tests { #[test] fn test_expect_eof_timeout() -> Result<(), Error> { - let mut p = spawn("sleep 3", Some(1000)).expect("cannot run sleep 3"); + let mut p = + spawn("sleep 3", Some(time::Duration::from_secs(1))).expect("cannot run sleep 3"); match p.exp_eof() { Ok(_) => panic!("should raise Timeout"), Err(Error::Timeout { .. }) => {} @@ -534,13 +540,14 @@ mod tests { #[test] fn test_expect_eof_timeout2() { - let mut p = spawn("sleep 1", Some(1100)).expect("cannot run sleep 1"); + let mut p = + spawn("sleep 1", Some(time::Duration::from_millis(1100))).expect("cannot run sleep 1"); assert!(p.exp_eof().is_ok(), "expected eof"); } #[test] fn test_expect_string() -> Result<(), Error> { - let mut p = spawn("cat", Some(1000)).expect("cannot run cat"); + let mut p = spawn("cat", Some(time::Duration::from_secs(1))).expect("cannot run cat"); p.send_line("hello world!")?; p.exp_string("hello world!")?; p.send_line("hello heaven!")?; @@ -550,7 +557,7 @@ mod tests { #[test] fn test_read_string_before() -> Result<(), Error> { - let mut p = spawn("cat", Some(1000)).expect("cannot run cat"); + let mut p = spawn("cat", Some(time::Duration::from_secs(1))).expect("cannot run cat"); p.send_line("lorem ipsum dolor sit amet")?; assert_eq!("lorem ipsum dolor sit ", p.exp_string("amet")?); Ok(()) @@ -558,7 +565,7 @@ mod tests { #[test] fn test_expect_any() -> Result<(), Error> { - let mut p = spawn("cat", Some(1000)).expect("cannot run cat"); + let mut p = spawn("cat", Some(time::Duration::from_secs(1))).expect("cannot run cat"); p.send_line("Hi")?; match p.exp_any(vec![ ReadUntil::NBytes(3), @@ -572,7 +579,7 @@ mod tests { #[test] fn test_expect_empty_command_error() { - let p = spawn("", Some(1000)); + let p = spawn("", Some(time::Duration::from_secs(1))); match p { Ok(_) => panic!("should raise an error"), Err(Error::EmptyProgramName) => {} @@ -582,7 +589,7 @@ mod tests { #[test] fn test_kill_timeout() -> Result<(), Error> { - let mut p = spawn_bash(Some(1000))?; + let mut p = spawn_bash(Some(time::Duration::from_secs(1)))?; p.execute("cat <(echo ready) -", "ready")?; Ok(()) // p is dropped here and kill is sent immediately to bash @@ -591,7 +598,7 @@ mod tests { #[test] fn test_bash() -> Result<(), Error> { - let mut p = spawn_bash(Some(1000))?; + let mut p = spawn_bash(Some(time::Duration::from_secs(1)))?; p.send_line("cd /tmp/")?; p.wait_for_prompt()?; p.send_line("pwd")?; @@ -601,7 +608,7 @@ mod tests { #[test] fn test_bash_control_chars() -> Result<(), Error> { - let mut p = spawn_bash(Some(1000))?; + let mut p = spawn_bash(Some(time::Duration::from_secs(1)))?; p.execute("cat <(echo ready) -", "ready")?; p.send_control('c')?; // abort: SIGINT p.wait_for_prompt()?; From f655c79d53c2f0774a25346d1e3b56a16f799b3c Mon Sep 17 00:00:00 2001 From: LightVillet Date: Sun, 15 Mar 2026 00:32:30 +0200 Subject: [PATCH 3/3] feat: Add configurable `poll_frequency` --- src/reader.rs | 18 +++++++++++++++++- src/session.rs | 14 ++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/reader.rs b/src/reader.rs index b8b47d64..42011e6d 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -104,9 +104,12 @@ pub fn find(needle: &ReadUntil, buffer: &str, eof: bool) -> Option<(usize, usize /// + `None`: `read_until` is blocking forever. This is probably not what you want /// + `Some(millis)`: after millis milliseconds a timeout error is raised /// - `strip_ansi_escape_codes`: Whether to filter out escape codes, such as colors. +/// + `Some`: the frequency with which the process will check the output. +/// + `None`: will be 1/1000 of a `timeout` (or `100ms` if `timeout` is None) #[derive(Default)] pub struct Options { pub timeout: Option, + pub poll_frequency: Option, pub strip_ansi_escape_codes: bool, } @@ -120,6 +123,7 @@ pub struct NBReader { buffer: String, eof: bool, timeout: Option, + poll_frequency: time::Duration, } impl NBReader { @@ -167,6 +171,15 @@ impl NBReader { // don't do error handling as on an error it was most probably // the main thread which exited (remote hangup) }); + + // Set `poll_frequency` based on the `timeout` (or 100ms in case `timeout` is None) if it is None + let poll_frequency = match options.poll_frequency { + Some(poll_frequency) => poll_frequency, + None => options + .timeout + .map_or(time::Duration::from_millis(100), |t| t / 1000), + }; + // allocate string with a initial capacity of 1024, so when appending chars // we don't need to reallocate memory often NBReader { @@ -174,6 +187,7 @@ impl NBReader { buffer: String::with_capacity(1024), eof: false, timeout: options.timeout, + poll_frequency, } } @@ -279,7 +293,7 @@ impl NBReader { } } // nothing matched: wait a little - thread::sleep(time::Duration::from_millis(100)); + thread::sleep(self.poll_frequency); } } @@ -408,6 +422,7 @@ mod tests { Options { timeout: None, strip_ansi_escape_codes: true, + poll_frequency: None, }, ); let bytes = r @@ -425,6 +440,7 @@ mod tests { Options { timeout: None, strip_ansi_escape_codes: true, + poll_frequency: None, }, ); let bytes = r diff --git a/src/session.rs b/src/session.rs index f716b852..623f38d2 100644 --- a/src/session.rs +++ b/src/session.rs @@ -210,6 +210,9 @@ pub struct Builder { /// If Some: all `exp_*` commands time out after `timeout`, if None: never times out. /// By default timeout is set to 30s pub(super) timeout: Option, + /// If Some: the frequency with which the process will check the output. + /// If None: will be 1/1000 of a `timeout` (or `100ms` if `timeout` is None) + pub(super) poll_frequency: Option, /// Whether to filter out escape codes, such as colors. pub(super) strip_ansi_escape_codes: bool, } @@ -241,6 +244,14 @@ impl Builder { self } + /// Set the poll frequency with which the process will check the output. + /// + /// Keep `None` to make it relative to the `timeout`. + pub fn poll_frequency(mut self, poll_frequency: Option) -> Self { + self.poll_frequency = poll_frequency; + self + } + /// Set filtering out escape codes, such as colors. pub fn strip_ansi_escape_codes(mut self) -> Self { self.strip_ansi_escape_codes = true; @@ -259,12 +270,14 @@ impl Builder { let Self { timeout, + poll_frequency, strip_ansi_escape_codes, .. } = self; let options = Options { timeout, + poll_frequency, strip_ansi_escape_codes, }; @@ -504,6 +517,7 @@ pub fn spawn_stream( Options { timeout, strip_ansi_escape_codes: false, + poll_frequency: None, }, ) }