use std::io::{self, BufRead, Read, Write};
#[cfg(unix)]
use std::process::Command;
use crate::{
error::Error,
process::{Healthcheck, Termios},
session::OsSession,
spawn, Captures, Expect, Needle,
};
#[cfg(feature = "async")]
use std::{
pin::Pin,
task::{Context, Poll},
};
#[cfg(feature = "async")]
use futures_lite::{io::AsyncBufRead, AsyncRead, AsyncWrite};
#[cfg(feature = "async")]
use crate::AsyncExpect;
#[cfg(unix)]
#[cfg(not(feature = "async"))]
pub fn spawn_bash() -> Result<ReplSession<OsSession>, Error> {
const DEFAULT_PROMPT: &str = "EXPECT_PROMPT";
let mut cmd = Command::new("bash");
let _ = cmd.env("PS1", DEFAULT_PROMPT);
let _ = cmd.env(
"PROMPT_COMMAND",
"PS1=EXPECT_PROMPT; unset PROMPT_COMMAND; bind 'set enable-bracketed-paste off'",
);
let session = crate::session::Session::spawn(cmd)?;
let mut bash = ReplSession::new(session, DEFAULT_PROMPT);
bash.set_quit_command("quit");
bash.expect_prompt()?;
Ok(bash)
}
#[cfg(unix)]
#[cfg(feature = "async")]
pub async fn spawn_bash() -> Result<ReplSession<OsSession>, Error> {
const DEFAULT_PROMPT: &str = "EXPECT_PROMPT";
let mut cmd = Command::new("bash");
let _ = cmd.env("PS1", DEFAULT_PROMPT);
let _ = cmd.env(
"PROMPT_COMMAND",
"PS1=EXPECT_PROMPT; unset PROMPT_COMMAND; bind 'set enable-bracketed-paste off'",
);
let session = crate::session::Session::spawn(cmd)?;
let mut bash = ReplSession::new(session, DEFAULT_PROMPT);
bash.set_quit_command("quit");
bash.set_echo(false);
bash.expect_prompt().await?;
Ok(bash)
}
#[cfg(not(feature = "async"))]
pub fn spawn_python() -> Result<ReplSession<OsSession>, Error> {
let session = spawn("python")?;
let mut idle = ReplSession::new(session, ">>> ");
idle.set_quit_command("quit()");
idle.expect_prompt()?;
Ok(idle)
}
#[cfg(feature = "async")]
pub async fn spawn_python() -> Result<ReplSession<OsSession>, Error> {
let session = spawn("python")?;
let mut idle = ReplSession::new(session, ">>> ");
idle.set_quit_command("quit()");
idle.set_echo(false);
idle.expect_prompt().await?;
Ok(idle)
}
#[cfg(windows)]
#[cfg(not(feature = "async"))]
pub fn spawn_powershell() -> Result<ReplSession<OsSession>, Error> {
const DEFAULT_PROMPT: &str = "EXPECTED_PROMPT>";
let session = spawn("pwsh -NoProfile -NonInteractive -NoLogo")?;
let mut powershell = ReplSession::new(session, DEFAULT_PROMPT);
powershell.set_quit_command("exit");
powershell.set_echo(true);
let _ = powershell.execute(format!(
r#"function prompt {{ "{}"; return " " }}"#,
DEFAULT_PROMPT
))?;
let _ =
powershell.execute(r#"[System.Environment]::SetEnvironmentVariable("TERM", "dumb")"#)?;
let _ = powershell
.execute(r#"[System.Environment]::SetEnvironmentVariable("TERM", "NO_COLOR")"#)?;
Ok(powershell)
}
#[cfg(windows)]
#[cfg(feature = "async")]
pub async fn spawn_powershell() -> Result<ReplSession<OsSession>, Error> {
const DEFAULT_PROMPT: &str = "EXPECTED_PROMPT>";
let session = spawn("pwsh -NoProfile -NonInteractive -NoLogo")?;
let mut powershell = ReplSession::new(session, DEFAULT_PROMPT);
powershell.set_quit_command("exit");
powershell.set_echo(true);
let _ = powershell
.execute(format!(
r#"function prompt {{ "{}"; return " " }}"#,
DEFAULT_PROMPT
))
.await?;
let _ = powershell
.execute(r#"[System.Environment]::SetEnvironmentVariable("TERM", "dumb")"#)
.await?;
let _ = powershell
.execute(r#"[System.Environment]::SetEnvironmentVariable("TERM", "NO_COLOR")"#)
.await?;
Ok(powershell)
}
#[derive(Debug)]
pub struct ReplSession<S> {
session: S,
prompt: String,
quit_command: Option<String>,
is_echo_on: bool,
}
impl<S> ReplSession<S> {
pub fn new(session: S, prompt: impl Into<String>) -> Self {
Self {
session,
prompt: prompt.into(),
quit_command: None,
is_echo_on: false,
}
}
pub fn set_echo(&mut self, on: bool) {
self.is_echo_on = on;
}
pub fn set_quit_command(&mut self, cmd: impl Into<String>) {
self.quit_command = Some(cmd.into());
}
pub fn get_prompt(&self) -> &str {
&self.prompt
}
pub fn get_quit_command(&self) -> Option<&str> {
self.quit_command.as_deref()
}
pub fn is_echo(&self) -> bool {
self.is_echo_on
}
pub fn into_session(self) -> S {
self.session
}
pub fn get_session(&self) -> &S {
&self.session
}
pub fn get_session_mut(&mut self) -> &mut S {
&mut self.session
}
}
#[cfg(not(feature = "async"))]
impl<S> ReplSession<S>
where
S: Expect,
{
pub fn expect_prompt(&mut self) -> Result<(), Error> {
let _ = self._expect_prompt()?;
Ok(())
}
fn _expect_prompt(&mut self) -> Result<Captures, Error> {
self.session.expect(&self.prompt)
}
}
#[cfg(feature = "async")]
impl<S> ReplSession<S>
where
S: AsyncExpect + Unpin,
{
pub async fn expect_prompt(&mut self) -> Result<(), Error> {
let _ = self._expect_prompt().await?;
Ok(())
}
async fn _expect_prompt(&mut self) -> Result<Captures, Error> {
self.session.expect(&self.prompt).await
}
}
#[cfg(not(feature = "async"))]
impl<S> ReplSession<S>
where
S: Expect,
{
pub fn execute<C>(&mut self, cmd: C) -> Result<Vec<u8>, Error>
where
C: AsRef<str>,
{
self.send_line(cmd)?;
let found = self._expect_prompt()?;
let out = found.before().to_vec();
Ok(out)
}
#[cfg(not(feature = "async"))]
pub fn send_line<L>(&mut self, line: L) -> Result<(), Error>
where
L: AsRef<str>,
{
let text = line.as_ref();
self.session.send_line(text)?;
if self.is_echo_on {
let _ = self.get_session_mut().expect(line.as_ref())?;
}
Ok(())
}
pub fn exit(&mut self) -> Result<(), Error> {
if let Some(quit_command) = &self.quit_command {
self.session.send_line(quit_command)?;
}
Ok(())
}
}
#[cfg(feature = "async")]
impl<S> ReplSession<S>
where
S: AsyncExpect + Unpin,
{
pub async fn execute(&mut self, cmd: impl AsRef<str>) -> Result<Vec<u8>, Error> {
self.send_line(cmd).await?;
let found = self._expect_prompt().await?;
Ok(found.before().to_vec())
}
pub async fn send_line(&mut self, line: impl AsRef<str>) -> Result<(), Error> {
self.session.send_line(line.as_ref()).await?;
if self.is_echo_on {
let _ = self.expect(line.as_ref()).await?;
}
Ok(())
}
pub async fn exit(&mut self) -> Result<(), Error> {
if let Some(quit_command) = &self.quit_command {
self.session.send_line(quit_command).await?;
}
Ok(())
}
}
impl<S> Healthcheck for ReplSession<S>
where
S: Healthcheck,
{
type Status = S::Status;
fn get_status(&self) -> io::Result<Self::Status> {
self.get_session().get_status()
}
fn is_alive(&self) -> io::Result<bool> {
self.get_session().is_alive()
}
}
impl<S> Termios for ReplSession<S>
where
S: Termios,
{
fn is_echo(&self) -> io::Result<bool> {
self.get_session().is_echo()
}
fn set_echo(&mut self, on: bool) -> io::Result<bool> {
self.get_session_mut().set_echo(on)
}
}
impl<S> Expect for ReplSession<S>
where
S: Expect,
{
fn expect<N>(&mut self, needle: N) -> Result<Captures, Error>
where
N: Needle,
{
S::expect(self.get_session_mut(), needle)
}
fn check<N>(&mut self, needle: N) -> Result<Captures, Error>
where
N: Needle,
{
S::check(self.get_session_mut(), needle)
}
fn is_matched<N>(&mut self, needle: N) -> Result<bool, Error>
where
N: Needle,
{
S::is_matched(self.get_session_mut(), needle)
}
fn send<B>(&mut self, buf: B) -> Result<(), Error>
where
B: AsRef<[u8]>,
{
S::send(self.get_session_mut(), buf)
}
fn send_line<B>(&mut self, buf: B) -> Result<(), Error>
where
B: AsRef<[u8]>,
{
S::send_line(self.get_session_mut(), buf)
}
}
#[cfg(feature = "async")]
impl<S> AsyncExpect for ReplSession<S>
where
S: AsyncExpect,
{
async fn expect<N>(&mut self, needle: N) -> Result<Captures, Error>
where
N: Needle,
{
S::expect(self.get_session_mut(), needle).await
}
async fn check<N>(&mut self, needle: N) -> Result<Captures, Error>
where
N: Needle,
{
S::check(self.get_session_mut(), needle).await
}
async fn is_matched<N>(&mut self, needle: N) -> Result<bool, Error>
where
N: Needle,
{
S::is_matched(self.get_session_mut(), needle).await
}
async fn send<B>(&mut self, buf: B) -> Result<(), Error>
where
B: AsRef<[u8]>,
{
S::send(self.get_session_mut(), buf).await
}
async fn send_line<B>(&mut self, buf: B) -> Result<(), Error>
where
B: AsRef<[u8]>,
{
S::send_line(self.get_session_mut(), buf).await
}
}
impl<S> Write for ReplSession<S>
where
S: Write,
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
S::write(self.get_session_mut(), buf)
}
fn flush(&mut self) -> io::Result<()> {
S::flush(self.get_session_mut())
}
}
impl<S> Read for ReplSession<S>
where
S: Read,
{
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
S::read(self.get_session_mut(), buf)
}
}
impl<S> BufRead for ReplSession<S>
where
S: BufRead,
{
fn fill_buf(&mut self) -> io::Result<&[u8]> {
S::fill_buf(self.get_session_mut())
}
fn consume(&mut self, amt: usize) {
S::consume(self.get_session_mut(), amt)
}
}
#[cfg(feature = "async")]
impl<S> AsyncWrite for ReplSession<S>
where
S: AsyncWrite + Unpin,
{
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
S::poll_write(Pin::new(self.get_session_mut()), cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
S::poll_flush(Pin::new(self.get_session_mut()), cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
S::poll_close(Pin::new(self.get_session_mut()), cx)
}
}
#[cfg(feature = "async")]
impl<S> AsyncRead for ReplSession<S>
where
S: AsyncRead + Unpin,
{
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
S::poll_read(Pin::new(self.get_session_mut()), cx, buf)
}
}
#[cfg(feature = "async")]
impl<S> AsyncBufRead for ReplSession<S>
where
S: AsyncBufRead + Unpin,
{
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
S::poll_fill_buf(Pin::new(self.get_mut().get_session_mut()), cx)
}
fn consume(mut self: Pin<&mut Self>, amt: usize) {
S::consume(Pin::new(self.get_session_mut()), amt)
}
}