Skip to content

Commit

Permalink
[WIP] Move to trait based version
Browse files Browse the repository at this point in the history
  • Loading branch information
zhiburt committed Feb 27, 2024
1 parent e8b43ff commit 476f39b
Show file tree
Hide file tree
Showing 31 changed files with 909 additions and 606 deletions.
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,12 @@ crossbeam-channel = { version = "0.5.6", optional = true }

[package.metadata.docs.rs]
all-features = false

[[target.'cfg(unix)'.example]]
name = "log"
path = "examples/log.rs"

[[target.'cfg(windows)'.example]]
name = "powershell"
path = "examples/powershell.rs"

2 changes: 1 addition & 1 deletion examples/bash.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// An example is based on README.md from https://github.com/philippkeller/rexpect

#[cfg(unix)]
use expectrl::{repl::spawn_bash, ControlCode, Regex};
use expectrl::{repl::spawn_bash, ControlCode, Regex, Expect};

#[cfg(unix)]
#[cfg(not(feature = "async"))]
Expand Down
10 changes: 5 additions & 5 deletions examples/check.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
use expectrl::{check, spawn, Error};
use expectrl::{check, spawn, Error, Expect};

#[cfg(not(feature = "async"))]
fn main() {
let mut session = spawn("python ./tests/source/ansi.py").expect("Can't spawn a session");
let mut p = spawn("python ./tests/source/ansi.py").expect("Can't spawn a session");

loop {
match check!(
&mut session,
&mut p,
_ = "Password: " => {
println!("Set password to SECURE_PASSWORD");
session.send_line("SECURE_PASSWORD").unwrap();
p.send_line("SECURE_PASSWORD").unwrap();
},
_ = "Continue [y/n]:" => {
println!("Stop processing");
session.send_line("n").unwrap();
p.send_line("n").unwrap();
},
) {
Err(Error::Eof) => break,
Expand Down
6 changes: 3 additions & 3 deletions examples/expect_line.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use expectrl::{self, Any, Eof};
use expectrl::{self, Any, Eof, Expect};

#[cfg(not(feature = "async"))]
fn main() {
let mut session = expectrl::spawn("ls -al").expect("Can't spawn a session");
let mut p = expectrl::spawn("ls -al").expect("Can't spawn a session");

loop {
let m = session
let m = p
.expect(Any::boxed(vec![
Box::new("\r"),
Box::new("\n"),
Expand Down
2 changes: 1 addition & 1 deletion examples/ftp.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use expectrl::{spawn, ControlCode, Error, Regex};
use expectrl::{spawn, ControlCode, Error, Regex, Expect};

#[cfg(not(feature = "async"))]
fn main() -> Result<(), Error> {
Expand Down
37 changes: 19 additions & 18 deletions examples/ftp_interact.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
use expectrl::{
interact::{actions::lookup::Lookup, InteractOptions},
spawn,
stream::stdin::Stdin,
ControlCode, Error, Regex,
interact::actions::lookup::Lookup, spawn, stream::stdin::Stdin, ControlCode, Error, Expect,
Regex,
};
use std::io::stdout;

#[cfg(not(all(windows, feature = "polling")))]
#[cfg(not(feature = "async"))]
fn main() -> Result<(), Error> {
let mut p = spawn("ftp bks4-speedtest-1.tele2.net")?;

let mut auth = false;
let mut login_lookup = Lookup::new();
let opts = InteractOptions::new(&mut auth).on_output(|ctx| {
if login_lookup
.on(ctx.buf, ctx.eof, "Login successful")?
.is_some()
{
**ctx.state = true;
return Ok(true);
}

Ok(false)
});
let mut stdin = Stdin::open()?;

let mut p = spawn("ftp bks4-speedtest-1.tele2.net")?;
p.interact(&mut stdin, stdout())
.set_state(&mut auth)
.on_output(move |ctx| {
if login_lookup
.on(ctx.buf, ctx.eof, "Login successful")?
.is_some()
{
**ctx.state = true;
return Ok(true);
}

Ok(false)
})
.spawn()?;

let mut stdin = Stdin::open()?;
p.interact(&mut stdin, stdout()).spawn(opts)?;
stdin.close()?;

if !auth {
Expand Down
4 changes: 2 additions & 2 deletions examples/interact.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! To run an example run `cargo run --example interact`.
use expectrl::{interact::InteractOptions, spawn, stream::stdin::Stdin};
use expectrl::{spawn, stream::stdin::Stdin};
use std::io::stdout;

#[cfg(unix)]
Expand All @@ -20,7 +20,7 @@ fn main() {
let mut stdin = Stdin::open().expect("Failed to create stdin");

sh.interact(&mut stdin, stdout())
.spawn(&mut InteractOptions::default())
.spawn()
.expect("Failed to start interact");

stdin.close().expect("Failed to close a stdin");
Expand Down
81 changes: 40 additions & 41 deletions examples/interact_with_callback.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
use expectrl::{
interact::{actions::lookup::Lookup, InteractOptions},
spawn,
stream::stdin::Stdin,
Regex,
};
use expectrl::{interact::actions::lookup::Lookup, spawn, stream::stdin::Stdin, Regex};

#[derive(Debug, Default)]
struct State {
Expand All @@ -18,53 +13,57 @@ fn main() {
let mut output_action = Lookup::new();
let mut input_action = Lookup::new();
let mut state = State::default();
let opts = InteractOptions::new(&mut state)
.on_output(|mut ctx| {
let m = output_action.on(ctx.buf, ctx.eof, "Continue [y/n]:")?;
if m.is_some() {
ctx.state.wait_for_continue = Some(true);
};

let m = output_action.on(ctx.buf, ctx.eof, Regex("status:\\s*.*\\w+.*\\r\\n"))?;
if m.is_some() {
ctx.state.stutus_verification_counter =
Some(ctx.state.stutus_verification_counter.map_or(1, |c| c + 1));
output_action.clear();
}
let mut session = spawn("python ./tests/source/ansi.py").expect("Can't spawn a session");

Ok(false)
})
.on_input(|mut ctx| {
let m = input_action.on(ctx.buf, ctx.eof, "y")?;
if m.is_some() {
if let Some(_a @ true) = ctx.state.wait_for_continue {
ctx.state.pressed_yes_on_continue = Some(true);
}
};
let mut stdin = Stdin::open().unwrap();
let stdout = std::io::stdout();

let m = input_action.on(ctx.buf, ctx.eof, "n")?;
if m.is_some() {
if let Some(_a @ true) = ctx.state.wait_for_continue {
ctx.state.pressed_yes_on_continue = Some(false);
let (is_alive, status) = {
let mut interact = session.interact(&mut stdin, stdout).set_state(&mut state);
interact
.on_output(move |ctx| {
let m = output_action.on(ctx.buf, ctx.eof, "Continue [y/n]:")?;
if m.is_some() {
ctx.state.wait_for_continue = Some(true);
};

let m = output_action.on(ctx.buf, ctx.eof, Regex("status:\\s*.*\\w+.*\\r\\n"))?;
if m.is_some() {
ctx.state.stutus_verification_counter =
Some(ctx.state.stutus_verification_counter.map_or(1, |c| c + 1));
output_action.clear();
}
}

Ok(false)
});
Ok(false)
})
.on_input(move |ctx| {
let m = input_action.on(ctx.buf, ctx.eof, "y")?;
if m.is_some() {
if let Some(_a @ true) = ctx.state.wait_for_continue {
ctx.state.pressed_yes_on_continue = Some(true);
}
};

let m = input_action.on(ctx.buf, ctx.eof, "n")?;
if m.is_some() {
if let Some(_a @ true) = ctx.state.wait_for_continue {
ctx.state.pressed_yes_on_continue = Some(false);
}
}

let mut session = spawn("python ./tests/source/ansi.py").expect("Can't spawn a session");
Ok(false)
});

let mut stdin = Stdin::open().unwrap();
let stdout = std::io::stdout();

let mut interact = session.interact(&mut stdin, stdout);
let is_alive = interact.spawn().expect("Failed to start interact");

let is_alive = interact.spawn(opts).expect("Failed to start interact");
(is_alive, interact.get_status())
};

if !is_alive {
println!("The process was exited");
#[cfg(unix)]
println!("Status={:?}", interact.get_status());
println!("Status={:?}", status);
}

stdin.close().unwrap();
Expand Down
2 changes: 1 addition & 1 deletion examples/log.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use expectrl::{spawn, Error};
use expectrl::{spawn, Error, Expect};

fn main() -> Result<(), Error> {
let p = spawn("cat")?;
Expand Down
2 changes: 1 addition & 1 deletion examples/ping.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#[cfg(unix)]
use expectrl::{repl::spawn_bash, ControlCode, Error};
use expectrl::{repl::spawn_bash, ControlCode, Error, Expect};

#[cfg(unix)]
#[cfg(not(feature = "async"))]
Expand Down
5 changes: 3 additions & 2 deletions examples/powershell.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#[cfg(windows)]
fn main() {
use expectrl::{repl::spawn_powershell, ControlCode, Regex};
use expectrl::{repl::spawn_powershell, ControlCode, Regex};

#[cfg(windows)]
fn main() {
#[cfg(feature = "async")]
{
futures_lite::future::block_on(async {
Expand Down
2 changes: 1 addition & 1 deletion examples/python.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use expectrl::{repl::spawn_python, Regex};
use expectrl::{repl::spawn_python, Regex, Expect};

#[cfg(not(feature = "async"))]
fn main() {
Expand Down
11 changes: 7 additions & 4 deletions examples/shell.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use expectrl::repl::ReplSession;
use std::io::Result;
use expectrl::process::Termios;

#[cfg(all(unix, not(feature = "async")))]
fn main() -> Result<()> {
let mut p = expectrl::spawn("sh")?;
p.get_process_mut().set_echo(true, None)?;

let mut shell = ReplSession::new(p, String::from("sh-5.1$"), Some(String::from("exit")), true);
let mut p = expectrl::spawn("sh")?;
p.set_echo(true)?;

let mut shell = ReplSession::new(p, String::from("sh-5.1$"));
shell.set_echo(true);
shell.set_quit_command("exit");
shell.expect_prompt()?;

let output = exec(&mut shell, "echo Hello World")?;
Expand All @@ -20,7 +23,7 @@ fn main() -> Result<()> {
}

#[cfg(all(unix, not(feature = "async")))]
fn exec(shell: &mut ReplSession, cmd: &str) -> Result<String> {
fn exec(shell: &mut ReplSession<expectrl::session::OsSession>, cmd: &str) -> Result<String> {
let buf = shell.execute(cmd)?;
let mut string = String::from_utf8_lossy(&buf).into_owned();
string = string.replace("\r\n\u{1b}[?2004l\r", "");
Expand Down
2 changes: 1 addition & 1 deletion src/check_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ macro_rules! check {
// The question is which solution is more effichient.
// I took the following approach because there's no chance we influence user's land via the variable name we pick.
(@branch $session:expr, ($var:tt = $exp:expr => $body:tt, $($tail:tt)*), ($($default:tt)*)) => {
match $crate::session::Session::check($session, $exp) {
match $crate::Expect::check($session, $exp) {
result if result.as_ref().map(|found| !found.is_empty()).unwrap_or(false) => {
let $var = result.unwrap();
$body;
Expand Down
67 changes: 67 additions & 0 deletions src/expect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use crate::{Captures, Error, Needle};

type Result<T> = std::result::Result<T, Error>;

pub trait Expect {
fn expect<N>(&mut self, needle: N) -> Result<Captures>
where
N: Needle;

fn check<N>(&mut self, needle: N) -> Result<Captures>
where
N: Needle;

fn is_matched<N>(&mut self, needle: N) -> Result<bool>
where
N: Needle;

/// Send buffer to the stream.
fn send<B>(&mut self, buf: B) -> Result<()>
where
B: AsRef<[u8]>;

/// Send line to the stream.
fn send_line<B>(&mut self, buf: B) -> Result<()>
where
B: AsRef<[u8]>;
}

impl<T> Expect for &mut T
where
T: Expect,
{
fn expect<N>(&mut self, needle: N) -> Result<Captures>
where
N: Needle,
{
T::expect(self, needle)
}

fn check<N>(&mut self, needle: N) -> Result<Captures>
where
N: Needle,
{
T::check(self, needle)
}

fn is_matched<N>(&mut self, needle: N) -> Result<bool>
where
N: Needle,
{
T::is_matched(self, needle)
}

fn send<B>(&mut self, buf: B) -> Result<()>
where
B: AsRef<[u8]>,
{
T::send(self, buf)
}

fn send_line<B>(&mut self, buf: B) -> Result<()>
where
B: AsRef<[u8]>,
{
T::send_line(self, buf)
}
}
2 changes: 0 additions & 2 deletions src/interact/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@
pub mod actions;
mod context;
mod opts;
mod session;

pub use context::Context;
pub use opts::{InteractOptions, NoAction, NoFilter};
pub use session::InteractSession;
Loading

0 comments on commit 476f39b

Please sign in to comment.