Skip to content

Commit ab83799

Browse files
authored
Merge pull request #168 from epage/api2
feat: Extend API in prep for breaking changes
2 parents 89e9d4e + cd038c5 commit ab83799

File tree

4 files changed

+80
-59
lines changed

4 files changed

+80
-59
lines changed

examples/exit_code.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use rexpect::error::Error;
2-
use rexpect::process::wait;
2+
use rexpect::process::WaitStatus;
33
use rexpect::spawn;
44

55
/// The following code emits:
@@ -8,15 +8,15 @@ use rexpect::spawn;
88
/// Output (stdout and stderr): cat: /this/does/not/exist: No such file or directory
99
fn main() -> Result<(), Error> {
1010
let p = spawn("cat /etc/passwd", Some(2000))?;
11-
match p.process.wait() {
12-
Ok(wait::WaitStatus::Exited(_, 0)) => println!("cat exited with code 0, all good!"),
11+
match p.process().wait() {
12+
Ok(WaitStatus::Exited(_, 0)) => println!("cat exited with code 0, all good!"),
1313
_ => println!("cat exited with code >0, or it was killed"),
1414
}
1515

1616
let mut p = spawn("cat /this/does/not/exist", Some(2000))?;
17-
match p.process.wait() {
18-
Ok(wait::WaitStatus::Exited(_, 0)) => println!("cat succeeded"),
19-
Ok(wait::WaitStatus::Exited(_, c)) => {
17+
match p.process().wait() {
18+
Ok(WaitStatus::Exited(_, 0)) => println!("cat succeeded"),
19+
Ok(WaitStatus::Exited(_, c)) => {
2020
println!("Cat failed with exit code {c}");
2121
println!("Output (stdout and stderr): {}", p.exp_eof()?);
2222
}

src/process.rs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use nix;
55
use nix::fcntl::{OFlag, open};
66
use nix::libc::STDERR_FILENO;
77
use nix::pty::{PtyMaster, grantpt, posix_openpt, unlockpt};
8-
pub use nix::sys::{signal, wait};
98
use nix::sys::{stat, termios};
109
use nix::unistd::{
1110
ForkResult, Pid, close, dup, dup2_stderr, dup2_stdin, dup2_stdout, fork, setsid,
@@ -18,6 +17,10 @@ use std::os::unix::process::CommandExt;
1817
use std::process::Command;
1918
use std::{thread, time};
2019

20+
pub use nix::sys::{signal, wait};
21+
pub use signal::Signal;
22+
pub use wait::WaitStatus;
23+
2124
/// Start a process in a forked tty to interact with it like you would
2225
/// within a terminal
2326
///
@@ -42,8 +45,7 @@ use std::{thread, time};
4245
/// # fn main() {
4346
///
4447
/// let mut process = PtyProcess::new(Command::new("cat")).expect("could not execute cat");
45-
/// let fd = dup(&process.pty).unwrap();
46-
/// let f = File::from(fd);
48+
/// let f = process.get_file_handle().unwrap();
4749
/// let mut writer = LineWriter::new(&f);
4850
/// let mut reader = BufReader::new(&f);
4951
/// process.exit().expect("could not terminate process");
@@ -161,7 +163,7 @@ impl PtyProcess {
161163
/// # Example
162164
/// ```rust,no_run
163165
///
164-
/// use rexpect::process::{self, wait::WaitStatus};
166+
/// use rexpect::process::{self, WaitStatus};
165167
/// use std::process::Command;
166168
///
167169
/// # fn main() {
@@ -173,26 +175,26 @@ impl PtyProcess {
173175
/// # }
174176
/// ```
175177
///
176-
pub fn status(&self) -> Option<wait::WaitStatus> {
178+
pub fn status(&self) -> Option<WaitStatus> {
177179
wait::waitpid(self.child_pid, Some(wait::WaitPidFlag::WNOHANG)).ok()
178180
}
179181

180182
/// Wait until process has exited (non-blocking).
181183
///
182184
/// If the process doesn't terminate this will block forever.
183-
pub fn wait(&self) -> Result<wait::WaitStatus, Error> {
185+
pub fn wait(&self) -> Result<WaitStatus, Error> {
184186
wait::waitpid(self.child_pid, None).map_err(Error::from)
185187
}
186188

187189
/// Regularly exit the process (blocking).
188190
///
189191
/// This method is blocking until the process is dead
190-
pub fn exit(&mut self) -> Result<wait::WaitStatus, Error> {
192+
pub fn exit(&mut self) -> Result<WaitStatus, Error> {
191193
self.kill(signal::SIGTERM)
192194
}
193195

194196
/// Kill the process with a specific signal (non-blocking).
195-
pub fn signal(&mut self, sig: signal::Signal) -> Result<(), Error> {
197+
pub fn signal(&mut self, sig: Signal) -> Result<(), Error> {
196198
signal::kill(self.child_pid, sig).map_err(Error::from)
197199
}
198200

@@ -206,26 +208,26 @@ impl PtyProcess {
206208
///
207209
/// If `kill_timeout` is set and a repeated sending of signal does not result in the process
208210
/// being killed, then `kill -9` is sent after the `kill_timeout` duration has elapsed.
209-
pub fn kill(&mut self, sig: signal::Signal) -> Result<wait::WaitStatus, Error> {
211+
pub fn kill(&mut self, sig: Signal) -> Result<WaitStatus, Error> {
210212
let start = time::Instant::now();
211213
loop {
212214
match signal::kill(self.child_pid, sig) {
213215
Ok(_) => {}
214216
// process was already killed before -> ignore
215217
Err(nix::errno::Errno::ESRCH) => {
216-
return Ok(wait::WaitStatus::Exited(Pid::from_raw(0), 0));
218+
return Ok(WaitStatus::Exited(Pid::from_raw(0), 0));
217219
}
218220
Err(e) => return Err(Error::from(e)),
219221
}
220222

221223
match self.status() {
222-
Some(status) if status != wait::WaitStatus::StillAlive => return Ok(status),
224+
Some(status) if status != WaitStatus::StillAlive => return Ok(status),
223225
Some(_) | None => thread::sleep(time::Duration::from_millis(100)),
224226
}
225227
// kill -9 if timeout is reached
226228
if let Some(timeout) = self.kill_timeout {
227229
if start.elapsed() > timeout {
228-
signal::kill(self.child_pid, signal::Signal::SIGKILL).map_err(Error::from)?;
230+
signal::kill(self.child_pid, Signal::SIGKILL).map_err(Error::from)?;
229231
}
230232
}
231233
}
@@ -234,7 +236,7 @@ impl PtyProcess {
234236

235237
impl Drop for PtyProcess {
236238
fn drop(&mut self) {
237-
if let Some(wait::WaitStatus::StillAlive) = self.status() {
239+
if let Some(WaitStatus::StillAlive) = self.status() {
238240
self.exit().expect("cannot exit");
239241
}
240242
}
@@ -243,7 +245,7 @@ impl Drop for PtyProcess {
243245
#[cfg(test)]
244246
mod tests {
245247
use super::*;
246-
use nix::sys::{signal, wait};
248+
use nix::sys::wait;
247249
use std::io::{BufRead, BufReader, LineWriter, Write};
248250

249251
#[test]
@@ -263,7 +265,7 @@ mod tests {
263265
thread::sleep(time::Duration::from_millis(100));
264266
writer.write_all(&[3])?; // send ^C
265267
writer.flush()?;
266-
let should = wait::WaitStatus::Signaled(process.child_pid, signal::Signal::SIGINT, false);
268+
let should = WaitStatus::Signaled(process.child_pid, Signal::SIGINT, false);
267269
assert_eq!(should, wait::waitpid(process.child_pid, None).unwrap());
268270
Ok(())
269271
}

src/reader.rs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ pub struct Options {
1919
pub strip_ansi_escape_codes: bool,
2020
}
2121

22+
impl Options {
23+
pub fn new() -> Self {
24+
Default::default()
25+
}
26+
27+
pub fn timeout_ms(mut self, timeout_ms: Option<u64>) -> Self {
28+
self.timeout_ms = timeout_ms;
29+
self
30+
}
31+
32+
pub fn strip_ansi_escape_codes(mut self, yes: bool) -> Self {
33+
self.strip_ansi_escape_codes = yes;
34+
self
35+
}
36+
}
37+
2238
/// Non blocking reader
2339
///
2440
/// Typically you'd need that to check for output of a process without blocking your thread.
@@ -408,13 +424,7 @@ mod tests {
408424
#[test]
409425
fn test_skip_partial_ansi_code() {
410426
let f = io::Cursor::new("\x1b[31;1;4mHello\x1b[1");
411-
let mut r = NBReader::new(
412-
f,
413-
Options {
414-
timeout_ms: None,
415-
strip_ansi_escape_codes: true,
416-
},
417-
);
427+
let mut r = NBReader::new(f, Options::new().strip_ansi_escape_codes(true));
418428
let bytes = r
419429
.read_until(&ReadUntil::String("Hello".to_owned()))
420430
.unwrap();
@@ -425,13 +435,7 @@ mod tests {
425435
#[test]
426436
fn test_skip_ansi_codes() {
427437
let f = io::Cursor::new("\x1b[31;1;4mHello\x1b[0m");
428-
let mut r = NBReader::new(
429-
f,
430-
Options {
431-
timeout_ms: None,
432-
strip_ansi_escape_codes: true,
433-
},
434-
);
438+
let mut r = NBReader::new(f, Options::new().strip_ansi_escape_codes(true));
435439
let bytes = r
436440
.read_until(&ReadUntil::String("Hello".to_owned()))
437441
.unwrap();

src/session.rs

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ impl PtySession {
210210
let stream = StreamSession::new(reader, f, options);
211211
Ok(Self { process, stream })
212212
}
213+
214+
pub fn process(&self) -> &PtyProcess {
215+
&self.process
216+
}
217+
218+
pub fn process_mut(&mut self) -> &mut PtyProcess {
219+
&mut self.process
220+
}
213221
}
214222

215223
/// Start command in background in a pty session (pty fork) and return a struct
@@ -245,13 +253,7 @@ fn tokenize_command(program: &str) -> Result<Vec<String>, Error> {
245253

246254
/// See [`spawn`]
247255
pub fn spawn_command(command: Command, timeout_ms: Option<u64>) -> Result<PtySession, Error> {
248-
spawn_with_options(
249-
command,
250-
Options {
251-
timeout_ms,
252-
strip_ansi_escape_codes: false,
253-
},
254-
)
256+
spawn_with_options(command, Options::new().timeout_ms(timeout_ms))
255257
}
256258

257259
/// See [`spawn`]
@@ -271,25 +273,45 @@ pub fn spawn_with_options(command: Command, options: Options) -> Result<PtySessi
271273
/// You have a prompt where a user inputs commands and the shell
272274
/// executes it and writes some output
273275
pub struct PtyReplSession {
274-
/// The prompt, used for `wait_for_prompt`, e.g. ">>> " for python
276+
pub pty_session: PtySession,
275277
pub prompt: String,
278+
pub quit_command: Option<String>,
279+
pub echo_on: bool,
280+
}
276281

277-
/// The `pty_session` you prepared before (initiating the shell, maybe set a custom prompt, etc.)
282+
impl PtyReplSession {
283+
/// Start a REPL session
284+
///
285+
/// `prompt`: used for [`Self::wait_for_prompt`], e.g. ">>> " for python
278286
///
279287
/// See [`spawn_bash`] for an example
280-
pub pty_session: PtySession,
288+
pub fn new(pty_session: PtySession, prompt: String) -> Self {
289+
Self {
290+
pty_session,
291+
prompt,
292+
quit_command: None,
293+
echo_on: false,
294+
}
295+
}
281296

282-
/// If set, then the `quit_command` is called when this object is dropped
283-
/// you need to provide this if the shell you're testing is not killed by just sending
284-
/// SIGTERM
285-
pub quit_command: Option<String>,
297+
/// Called when this object is dropped.
298+
///
299+
/// You need to provide this if the shell you're testing is not killed by just sending
300+
/// SIGTERM.
301+
pub fn quit_command(mut self, cmd: Option<String>) -> Self {
302+
self.quit_command = cmd;
303+
self
304+
}
286305

287306
/// Set this to true if the repl has echo on (i.e. sends user input to stdout)
288307
///
289308
/// Although echo is set off at pty fork (see `PtyProcess::new`) a few repls still
290309
/// seem to be able to send output.
291310
/// You may need to try with true first, and if tests fail set this to false.
292-
pub echo_on: bool,
311+
pub fn echo_on(mut self, yes: bool) -> Self {
312+
self.echo_on = yes;
313+
self
314+
}
293315
}
294316

295317
impl PtyReplSession {
@@ -423,8 +445,8 @@ pub fn spawn_bash(timeout: Option<u64>) -> Result<PtyReplSession, Error> {
423445
spawn_command(c, timeout).and_then(|p| {
424446
let new_prompt = "[REXPECT_PROMPT>";
425447
let mut pb = PtyReplSession {
426-
prompt: new_prompt.to_owned(),
427448
pty_session: p,
449+
prompt: new_prompt.to_owned(),
428450
quit_command: Some("quit".to_owned()),
429451
echo_on: false,
430452
};
@@ -456,14 +478,7 @@ pub fn spawn_stream<R: Read + Send + 'static, W: Write>(
456478
writer: W,
457479
timeout_ms: Option<u64>,
458480
) -> StreamSession<W> {
459-
StreamSession::new(
460-
reader,
461-
writer,
462-
Options {
463-
timeout_ms,
464-
strip_ansi_escape_codes: false,
465-
},
466-
)
481+
StreamSession::new(reader, writer, Options::new().timeout_ms(timeout_ms))
467482
}
468483

469484
#[cfg(test)]

0 commit comments

Comments
 (0)