Skip to content

Commit 6b37f7e

Browse files
committed
Add option to skip ansi escape codes
ansi escape codes are just the byte 27 inserted in a string, followed by "[", zero or more numbers separated by ";" and ending with a letter. We optionally just skip those in our non-blocking reader in order to efficently ignore them during matching. Signed-off-by: phaer <[email protected]>
1 parent eee779b commit 6b37f7e

File tree

9 files changed

+94
-49
lines changed

9 files changed

+94
-49
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use rexpect::spawn;
3838
use rexpect::error::*;
3939
4040
fn do_ftp() -> Result<(), Error> {
41-
let mut p = spawn("ftp speedtest.tele2.net", Some(30_000))?;
41+
let mut p = spawn("ftp speedtest.tele2.net", Some(30_000), false)?;
4242
p.exp_regex("Name \\(.*\\):")?;
4343
p.send_line("anonymous")?;
4444
p.exp_string("Password")?;
@@ -65,7 +65,7 @@ use rexpect::spawn_bash;
6565
use rexpect::error::*;
6666
6767
fn do_bash() -> Result<(), Error> {
68-
let mut p = spawn_bash(Some(2000))?;
68+
let mut p = spawn_bash(Some(2000), false)?;
6969
7070
// case 1: wait until program is done
7171
p.send_line("hostname")?;
@@ -115,7 +115,7 @@ use rexpect::spawn_bash;
115115
use rexpect::error::*;
116116
117117
fn do_bash_jobcontrol() -> Result<(), Error> {
118-
let mut p = spawn_bash(Some(1000))?;
118+
let mut p = spawn_bash(Some(1000), false)?;
119119
p.execute("ping 8.8.8.8", "bytes of data")?;
120120
p.send_control('z')?;
121121
p.wait_for_prompt()?;

examples/bash.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rexpect::error::Error;
22
use rexpect::spawn_bash;
33

44
fn main() -> Result<(), Error> {
5-
let mut p = spawn_bash(Some(1000))?;
5+
let mut p = spawn_bash(Some(1000), false)?;
66
p.execute("ping 8.8.8.8", "bytes")?;
77
p.send_control('z')?;
88
p.wait_for_prompt()?;

examples/bash_read.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rexpect::error::Error;
22
use rexpect::spawn_bash;
33

44
fn main() -> Result<(), Error> {
5-
let mut p = spawn_bash(Some(2000))?;
5+
let mut p = spawn_bash(Some(2000), false)?;
66

77
// case 1: wait until program is done
88
p.send_line("hostname")?;

examples/exit_code.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ use rexpect::spawn;
77
/// cat exited with code 1
88
/// Output (stdout and stderr): cat: /this/does/not/exist: No such file or directory
99
fn main() -> Result<(), Error> {
10-
let p = spawn("cat /etc/passwd", Some(2000))?;
10+
let p = spawn("cat /etc/passwd", Some(2000), false)?;
1111
match p.process.wait() {
1212
Ok(wait::WaitStatus::Exited(_, 0)) => println!("cat exited with code 0, all good!"),
1313
_ => println!("cat exited with code >0, or it was killed"),
1414
}
1515

16-
let mut p = spawn("cat /this/does/not/exist", Some(2000))?;
16+
let mut p = spawn("cat /this/does/not/exist", Some(2000), false)?;
1717
match p.process.wait() {
1818
Ok(wait::WaitStatus::Exited(_, 0)) => println!("cat succeeded"),
1919
Ok(wait::WaitStatus::Exited(_, c)) => {

examples/ftp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rexpect::error::Error;
22
use rexpect::spawn;
33

44
fn main() -> Result<(), Error> {
5-
let mut p = spawn("ftp speedtest.tele2.net", Some(2000))?;
5+
let mut p = spawn("ftp speedtest.tele2.net", Some(2000), false)?;
66
p.exp_regex("Name \\(.*\\):")?;
77
p.send_line("anonymous")?;
88
p.exp_string("Password")?;

examples/repl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ fn ed_session() -> Result<PtyReplSession, Error> {
1212

1313
// used for `wait_for_prompt()`
1414
prompt: "> ".to_string(),
15-
pty_session: spawn("/bin/ed -p '> '", Some(2000))?,
15+
pty_session: spawn("/bin/ed -p '> '", Some(2000), false)?,
1616
// command which is sent when the instance of this struct is dropped
1717
// in the below example this is not needed, but if you don't explicitly
1818
// exit a REPL then rexpect tries to send a SIGTERM and depending on the repl

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
//! use rexpect::error::Error;
2121
//!
2222
//! fn main() -> Result<(), Error> {
23-
//! let mut p = spawn("ftp speedtest.tele2.net", Some(2000))?;
23+
//! let mut p = spawn("ftp speedtest.tele2.net", Some(2000), false)?;
2424
//! p.exp_regex("Name \\(.*\\):")?;
2525
//! p.send_line("anonymous")?;
2626
//! p.exp_string("Password")?;
@@ -50,7 +50,7 @@
5050
//! use rexpect::error::Error;
5151
//!
5252
//! fn main() -> Result<(), Error> {
53-
//! let mut p = spawn_bash(Some(30_000))?;
53+
//! let mut p = spawn_bash(Some(30_000), false)?;
5454
//! p.execute("ping 8.8.8.8", "bytes of data")?;
5555
//! p.send_control('z')?;
5656
//! p.wait_for_prompt()?;

src/reader.rs

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,19 @@ impl NBReader {
119119
/// - timeout:
120120
/// + `None`: read_until is blocking forever. This is probably not what you want
121121
/// + `Some(millis)`: after millis milliseconds a timeout error is raised
122-
pub fn new<R: Read + Send + 'static>(f: R, timeout: Option<u64>) -> NBReader {
122+
/// - strip_ansi_escape_codes: Whether to filter out escape codes, such as colors.
123+
pub fn new<R: Read + Send + 'static>(
124+
f: R,
125+
timeout: Option<u64>,
126+
strip_ansi_escape_codes: bool
127+
) -> NBReader {
123128
let (tx, rx) = channel();
124129

125130
// spawn a thread which reads one char and sends it to tx
126131
thread::spawn(move || -> Result<(), Error> {
127132
let mut reader = BufReader::new(f);
128133
let mut byte = [0u8];
134+
129135
loop {
130136
match reader.read(&mut byte) {
131137
Ok(0) => {
@@ -134,12 +140,21 @@ impl NBReader {
134140
break;
135141
}
136142
Ok(_) => {
137-
tx.send(Ok(PipedChar::Char(byte[0])))
138-
.map_err(|_| Error::MpscSendError)?;
143+
if strip_ansi_escape_codes && byte[0] == 27 {
144+
while let Ok(_) = reader.read(&mut byte) {
145+
if char::from(byte[0]).is_alphabetic() {
146+
break;
147+
}
148+
}
149+
}
150+
else {
151+
tx.send(Ok(PipedChar::Char(byte[0])))
152+
.map_err(|_| Error::MpscSendError)?;
153+
}
139154
}
140155
Err(error) => {
141156
tx.send(Err(PipeError::IO(error)))
142-
.map_err(|_| Error::MpscSendError)?;
157+
.map_err(|_| Error::MpscSendError)?;
143158
}
144159
}
145160
}
@@ -208,7 +223,7 @@ impl NBReader {
208223
/// // instead of a Cursor you would put your process output or file here
209224
/// let f = Cursor::new("Hello, miss!\n\
210225
/// What do you mean: 'miss'?");
211-
/// let mut e = NBReader::new(f, None);
226+
/// let mut e = NBReader::new(f, None, false);
212227
///
213228
/// let (first_line, _) = e.read_until(&ReadUntil::String('\n'.to_string())).unwrap();
214229
/// assert_eq!("Hello, miss!", &first_line);
@@ -230,6 +245,7 @@ impl NBReader {
230245

231246
loop {
232247
self.read_into_buffer()?;
248+
233249
if let Some(tuple_pos) = find(needle, &self.buffer, self.eof) {
234250
let first = self.buffer.drain(..tuple_pos.0).collect();
235251
let second = self.buffer.drain(..tuple_pos.1 - tuple_pos.0).collect();
@@ -287,7 +303,7 @@ mod tests {
287303
#[test]
288304
fn test_expect_melon() {
289305
let f = io::Cursor::new("a melon\r\n");
290-
let mut r = NBReader::new(f, None);
306+
let mut r = NBReader::new(f, None, false);
291307
assert_eq!(
292308
("a melon".to_string(), "\r\n".to_string()),
293309
r.read_until(&ReadUntil::String("\r\n".to_string()))
@@ -304,7 +320,7 @@ mod tests {
304320
#[test]
305321
fn test_regex() {
306322
let f = io::Cursor::new("2014-03-15");
307-
let mut r = NBReader::new(f, None);
323+
let mut r = NBReader::new(f, None, false);
308324
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
309325
assert_eq!(
310326
("".to_string(), "2014-03-15".to_string()),
@@ -316,7 +332,7 @@ mod tests {
316332
#[test]
317333
fn test_regex2() {
318334
let f = io::Cursor::new("2014-03-15");
319-
let mut r = NBReader::new(f, None);
335+
let mut r = NBReader::new(f, None, false);
320336
let re = Regex::new(r"-\d{2}-").unwrap();
321337
assert_eq!(
322338
("2014".to_string(), "-03-".to_string()),
@@ -328,7 +344,7 @@ mod tests {
328344
#[test]
329345
fn test_nbytes() {
330346
let f = io::Cursor::new("abcdef");
331-
let mut r = NBReader::new(f, None);
347+
let mut r = NBReader::new(f, None, false);
332348
assert_eq!(
333349
("".to_string(), "ab".to_string()),
334350
r.read_until(&ReadUntil::NBytes(2)).expect("2 bytes")
@@ -346,7 +362,7 @@ mod tests {
346362
#[test]
347363
fn test_any_with_multiple_possible_matches() {
348364
let f = io::Cursor::new("zero one two three four five");
349-
let mut r = NBReader::new(f, None);
365+
let mut r = NBReader::new(f, None, false);
350366

351367
let result = r
352368
.read_until(&ReadUntil::Any(vec![
@@ -361,7 +377,7 @@ mod tests {
361377
#[test]
362378
fn test_any_with_same_start_different_length() {
363379
let f = io::Cursor::new("hi hello");
364-
let mut r = NBReader::new(f, None);
380+
let mut r = NBReader::new(f, None, false);
365381

366382
let result = r
367383
.read_until(&ReadUntil::Any(vec![
@@ -376,18 +392,29 @@ mod tests {
376392
#[test]
377393
fn test_eof() {
378394
let f = io::Cursor::new("lorem ipsum dolor sit amet");
379-
let mut r = NBReader::new(f, None);
395+
let mut r = NBReader::new(f, None, false);
380396
r.read_until(&ReadUntil::NBytes(2)).expect("2 bytes");
381397
assert_eq!(
382398
("".to_string(), "rem ipsum dolor sit amet".to_string()),
383399
r.read_until(&ReadUntil::EOF).expect("reading until EOF")
384400
);
385401
}
386402

403+
#[test]
404+
fn test_skip_ansi_codes() {
405+
let f = io::Cursor::new("\x1b[31;1;4mHello\x1b[0m");
406+
let mut r = NBReader::new(f, None, true);
407+
let bytes = r.read_until(&ReadUntil::String("Hello".to_string())).unwrap();
408+
assert_eq!(bytes, ("".to_string(), "Hello".to_string()));
409+
assert_eq!(None, r.try_read());
410+
411+
}
412+
413+
387414
#[test]
388415
fn test_try_read() {
389416
let f = io::Cursor::new("lorem");
390-
let mut r = NBReader::new(f, None);
417+
let mut r = NBReader::new(f, None, false);
391418
let bytes = r.read_until(&ReadUntil::NBytes(4)).unwrap();
392419
assert!(bytes.0.is_empty());
393420
assert_eq!(bytes.1, "lore");

0 commit comments

Comments
 (0)