diff --git a/examples/interact.rs b/examples/interact.rs index 659fe5c..c9dd704 100644 --- a/examples/interact.rs +++ b/examples/interact.rs @@ -7,11 +7,16 @@ use expectrl::repl::spawn_bash; #[cfg(unix)] #[cfg(not(feature = "async"))] fn main() { - let mut bash = spawn_bash().expect("Error while spawning bash"); + use expectrl::{Session, repl, spawn}; + let mut bash = repl::spawn_powershell().expect("Error while spawning bash"); + + // let mut bash = spawn("pwsh").expect("Error while spawning bash"); println!("Now you're in interacting mode"); println!("To return control back to main type CTRL-]"); + println!("ECHO IS {}", bash.get_echo().unwrap()); + let status = bash.interact().expect("Failed to start interact"); println!("Quiting status {:?}", status); diff --git a/examples/powershell.rs b/examples/powershell.rs index 0662b54..1ee7347 100644 --- a/examples/powershell.rs +++ b/examples/powershell.rs @@ -1,7 +1,7 @@ +use expectrl::{repl::spawn_powershell, ControlCode, Regex}; + #[cfg(windows)] fn main() { - use expectrl::{repl::spawn_powershell, ControlCode, Regex}; - let mut p = spawn_powershell().unwrap(); // case 1: execute @@ -41,6 +41,53 @@ fn main() { } #[cfg(not(windows))] +#[cfg(not(feature = "async"))] +fn main() { + + let mut p = spawn_powershell().unwrap(); + + use std::io::Write; + + // case 1: execute + let hostname = p.write_all(b"hostname\r").unwrap(); + p.write_all("\u{1b}[11;68R".as_bytes()).unwrap(); + let hostname = p.expect("EXPECTED_PROMPT>"); + let hostname = p.expect("EXPECTED_PROMPT>").unwrap().before().to_vec(); + println!( + "Current hostname: {:?}", + String::from_utf8(hostname).unwrap() + ); + + // case 2: wait until done, only extract a few infos + p.send_line("wc /etc/passwd").unwrap(); + // `exp_regex` returns both string-before-match and match itself, discard first + let lines = p.expect(Regex("[0-9]+")).unwrap(); + let words = p.expect(Regex("[0-9]+")).unwrap(); + let bytes = p.expect(Regex("[0-9]+")).unwrap(); + p.expect_prompt().unwrap(); // go sure `wc` is really done + println!( + "/etc/passwd has {} lines, {} words, {} chars", + String::from_utf8_lossy(lines.first()), + String::from_utf8_lossy(words.first()), + String::from_utf8_lossy(bytes.first()), + ); + + // case 3: read while program is still executing + p.send_line("ping 8.8.8.8").unwrap(); // returns when it sees "bytes of data" in output + for _ in 0..5 { + // times out if one ping takes longer than 2s + let duration = p.expect(Regex("[0-9. ]+ ms")).unwrap(); + println!( + "Roundtrip time: {}", + String::from_utf8_lossy(duration.first()) + ); + } + + p.send_control(ControlCode::EOT).unwrap(); +} + +#[cfg(not(windows))] +#[cfg(feature = "async")] fn main() { panic!("An example doesn't supported on windows") } diff --git a/src/lib.rs b/src/lib.rs index 031dbbb..229990d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,17 @@ -//! Expectrl a library for running, controlling and communicating with a process. +//! # A tool for automating terminal applications on Unix and on Windows. //! -//! It supports `async/await`. To use it you should specify a `async` feature. +//! Using the library you can: //! -//! # Example +//! - Spawn process +//! - Control process +//! - Interact with process's IO(input/output). +//! +//! `expectrl` like original `expect` may shine when you're working with interactive applications. +//! If your application is not interactive you may not find the library the best choise. +//! +//! ## Example +//! +//! An example for interacting via ftp. //! //! ```no_run,ignore //! use expectrl::{spawn, Regex, Eof, WaitStatus}; @@ -21,6 +30,17 @@ //! p.expect(Eof).unwrap(); //! assert_eq!(p.wait().unwrap(), WaitStatus::Exited(p.pid(), 0)); //! ``` +//! +//! *The example inspired by the one in [philippkeller/rexpect].* +//! +//! [For more examples, check the examples directory.](https://github.com/zhiburt/expectrl/tree/main/examples) +//! +//! ## Features +//! +//! - It has an `async` support (To enable them you must turn on an `async` feature). +//! - It supports logging. +//! - It supports interact function. +//! - It has a Windows support. mod control_code; mod error; @@ -28,7 +48,7 @@ mod expect; #[cfg(feature = "log")] mod log; pub mod repl; -mod session; +pub mod session; mod stream; pub use control_code::ControlCode; @@ -43,10 +63,10 @@ pub use conpty::ProcAttr; pub use ptyprocess::{Signal, WaitStatus}; #[cfg(not(feature = "log"))] -pub type Session = session::Session; +pub use session::Session; #[cfg(feature = "log")] -pub type Session = log::SessionWithLog; +pub use log::SessionWithLog as Session; /// Spawn spawnes a new session. /// diff --git a/src/log.rs b/src/log.rs index aa01724..c9bc9a6 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,4 +1,4 @@ -//! A wrapper of Session to log a read/write operations +//! A wrapper of [Session] to log a read/write operations use crate::{error::Error, session::Session}; use std::{ diff --git a/src/repl.rs b/src/repl.rs index b03d86a..f043599 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,6 +1,7 @@ -use std::ops::{Deref, DerefMut}; +//! This module contains a list of special Sessions that can be spawned. use crate::{error::Error, session::Found, Session}; +use std::ops::{Deref, DerefMut}; #[cfg(unix)] use std::process::Command; @@ -107,6 +108,49 @@ pub fn spawn_powershell() -> Result { Ok(powershell) } +/// Spawn a powershell session. +#[cfg(unix)] +pub fn spawn_powershell() -> Result { + const DEFAULT_PROMPT: &str = "EXPECTED_PROMPT>"; + + let mut cmd = Command::new("pwsh"); + cmd.args(&["-NoProfile", "-NonInteractive", "-NoLogo"]); + + let mut powershell = ReplSession::spawn(cmd, DEFAULT_PROMPT, Some("exit"))?; + // powershell.is_echo_on = true; + + // some magic value after which powershell starts working + // use std::io::Write; + // powershell.write_all("\u{1b}[12;1R".as_bytes()).unwrap(); + + // // https://stackoverflow.com/questions/5725888/windows-powershell-changing-the-command-prompt + // powershell + // .send(format!( + // "function prompt {{ \"{}\"; return \" \" }}", + // DEFAULT_PROMPT + // )) + // .unwrap(); + + // powershell.write_all(b"\r\r\r").unwrap(); + + // https://stackoverflow.com/questions/69063656/is-it-possible-to-stop-powershell-wrapping-output-in-ansi-sequences/69063912#69063912 + // https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_ansi_terminals?view=powershell-7.2#disabling-ansi-output + // powershell + // let hostname = p.write_all(b"\r").unwrap(); + // .send("[System.Environment]::SetEnvironmentVariable(\"TERM\", \"dumb\")") + // .unwrap(); + // powershell.send("\r").unwrap(); + + // powershell + // .send("[System.Environment]::SetEnvironmentVariable(\"TERM\", \"NO_COLOR\")") + // .unwrap(); + // powershell.send("\r").unwrap(); + + // powershell.expect_prompt().unwrap(); + + Ok(powershell) +} + /// A repl session: e.g. bash or the python shell: /// you have a prompt where a user inputs commands and the shell /// which executes them and manages IO streams. @@ -222,6 +266,15 @@ impl ReplSession { Ok(()) } + #[cfg(not(feature = "async"))] + pub fn send>(&mut self, line: S) -> Result<(), Error> { + self.session.send(line.as_ref())?; + if self.is_echo_on { + self.expect(line.as_ref())?; + } + Ok(()) + } + /// Sends line to repl (and flush the output). /// /// If echo_on=true wait for the input to appear. diff --git a/src/session.rs b/src/session.rs index bf5e4ee..5dbff29 100644 --- a/src/session.rs +++ b/src/session.rs @@ -159,6 +159,7 @@ impl Session { if let Some(timeout) = self.expect_timeout { if start.elapsed() > timeout { self.stream.keep_in_buffer(&buf); + println!("INTERACT{:?}", String::from_utf8_lossy(&buf)); return Err(Error::ExpectTimeout); } } @@ -346,6 +347,8 @@ impl Session { return self.status().map_err(nix_error_to_io); } + println!("OUTPUT {:?}", String::from_utf8_lossy(&buf[..n])); + std::io::stdout().write_all(&buf[..n])?; std::io::stdout().flush()?; } @@ -361,6 +364,8 @@ impl Session { return self.status().map_err(nix_error_to_io); } + println!("INPUT {:?}", String::from_utf8_lossy(&buf[..n])); + for i in 0..n { // Ctrl-] if buf[i] == ControlCode::GroupSeparator.into() {