use std::{fmt::Display, io::Write, process::Command};
use async_trait::async_trait;
use tokio::io::{AsyncRead, AsyncWrite};
use crate::Result;
#[derive(Debug, Clone)]
pub struct WindowSize {
pub rows: u16,
pub cols: u16,
}
#[async_trait]
pub trait PtyWriter: AsyncWrite {
async fn window_change(&self, width: u16, height: u16) -> Result<()>;
async fn eof(&self) -> Result<()>;
}
pub type BoxedPtyWriter = Box<dyn PtyWriter + Send + Sync + Unpin>;
pub trait PtyReader: AsyncRead {}
pub type BoxedPtyReader = Box<dyn PtyReader + Send + Sync + Unpin>;
#[async_trait]
pub trait PtyCtl {
async fn wait(&mut self) -> Result<i32>;
}
pub type BoxedPtyCtl = Box<dyn PtyCtl + Send + Sync + Unpin>;
pub struct BoxedPty {
pub ctl: BoxedPtyCtl,
pub writer: BoxedPtyWriter,
pub reader: BoxedPtyReader,
}
impl BoxedPty {
pub fn new(
ctl: impl PtyCtl + Send + Sync + Unpin + 'static,
writer: impl PtyWriter + Send + Sync + Unpin + 'static,
reader: impl PtyReader + Send + Sync + Unpin + 'static,
) -> Self {
Self {
ctl: Box::new(ctl),
writer: Box::new(writer),
reader: Box::new(reader),
}
}
pub fn destruct(self) -> (BoxedPtyCtl, BoxedPtyWriter, BoxedPtyReader) {
(self.ctl, self.writer, self.reader)
}
}
#[derive(Debug, strum::EnumString, serde::Deserialize, strum::AsRefStr, PartialEq)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "lowercase")]
pub enum ScriptExecutor {
#[strum(serialize = "sh")]
Sh,
#[strum(serialize = "bash")]
Bash,
#[strum(serialize = "powershell")]
Powershell,
}
impl Display for ScriptExecutor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_ref())
}
}
impl ScriptExecutor {
pub fn prepare_clean(&self) -> Vec<u8> {
match self {
ScriptExecutor::Sh => b"\ntrap 'rm -f -- \"$0\"' EXIT;".to_vec(),
ScriptExecutor::Bash => b"\ntrap 'rm -f -- \"$0\"' EXIT;".to_vec(),
ScriptExecutor::Powershell => b"\r\nRemove-Item $MyInvocation.MyCommand.Path".to_vec(),
}
}
}
pub enum Script<'a, 'b> {
Whole(&'a str),
Split {
program: &'a str,
args: Box<dyn 'b + Iterator<Item = &'a str> + Send>,
},
Script {
executor: ScriptExecutor,
input: &'a str,
},
}
impl<'a, 'b> Script<'a, 'b> {
pub fn sh(input: &'a str) -> Self {
Script::Script {
executor: ScriptExecutor::Sh,
input,
}
}
pub fn powershell(input: &'a str) -> Self {
Script::Script {
executor: ScriptExecutor::Powershell,
input,
}
}
pub fn into_command(self) -> std::io::Result<Command> {
let cmd = match self {
Script::Whole(cmd) => {
let mut iter = cmd.split_whitespace();
let mut cmd = Command::new(iter.next().unwrap());
cmd.args(iter);
cmd
}
Script::Split { program, args } => {
let mut cmd = Command::new(program);
cmd.args(args);
cmd
}
Script::Script { executor, input } => {
let mut temp = match executor {
ScriptExecutor::Sh | ScriptExecutor::Bash => tempfile::NamedTempFile::new(),
ScriptExecutor::Powershell => tempfile::NamedTempFile::with_suffix(".ps1"),
}?;
temp.write_all(input.as_bytes())?;
temp.write_all(executor.prepare_clean().as_slice())?;
let path = temp.into_temp_path().keep()?;
let mut cmd = Command::new(executor.as_ref());
cmd.arg(path);
cmd
}
};
Ok(cmd)
}
}
impl<'a, 'b> From<&'b [&'a str]> for Script<'a, 'b> {
fn from(args: &'b [&'a str]) -> Self {
Script::Split {
program: args[0],
args: Box::new(args.iter().skip(1).copied()),
}
}
}
impl<'a> From<&'a str> for Script<'a, 'a> {
fn from(program: &'a str) -> Self {
Script::Whole(program)
}
}
impl<'a, 'b> Script<'a, 'b> {
pub fn new<I>(program: &'a str, args: I) -> Self
where
I: IntoIterator<Item = &'a str> + 'b,
<I as std::iter::IntoIterator>::IntoIter: Send,
{
Self::Split {
program,
args: Box::new(args.into_iter()),
}
}
}