Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

interactive commands in BashLang #367

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 67 additions & 49 deletions plugins/shrs_mux/src/lang/bash.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,80 @@
use std::{
cell::RefCell,
collections::HashMap,
fmt::format,
io::{BufRead, BufReader, Read, Write},
ops::Add,
os::unix::process::ExitStatusExt,
process::{Child, ChildStderr, ChildStdin, ChildStdout, Command, ExitStatus, Stdio},
sync::Arc,
};
use std::{process::Stdio, sync::Arc};

use shrs::{
lang::{Lexer, Token},
prelude::*,
use shrs::prelude::*;
use tokio::{
io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter},
process::{Child, ChildStdin, ChildStdout, Command},
runtime,
sync::{
mpsc::{self, Sender},
RwLock,
},
};

use crate::{
interpreter::{read_err, read_out},
MuxState,
};

// TODO all of this is copied from PythonLang, definitely abstract this code

pub struct BashLang {
instance: RefCell<Child>,
instance: Child,
/// Channel for writing to process
write_tx: Sender<String>,
runtime: runtime::Runtime,
}

impl BashLang {
pub fn new() -> Self {
let runtime = runtime::Runtime::new().unwrap();

let _guard = runtime.enter();

let mut instance = Command::new("bash")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to start bash process");

let stdout = instance.stdout.take().unwrap();
let stderr = instance.stderr.take().unwrap();
let stdin = instance.stdin.take().unwrap();

runtime.spawn(async {
let mut stdout_reader = BufReader::new(stdout).lines();
while let Some(line) = stdout_reader.next_line().await.unwrap() {
println!("{line}");
}
});

runtime.spawn(async {
let mut stderr_reader = BufReader::new(stderr).lines();
while let Some(line) = stderr_reader.next_line().await.unwrap() {
eprintln!("{line}");
}
});

let (write_tx, mut write_rx) = mpsc::channel::<String>(8);

runtime.spawn(async move {
let mut stdin_writer = BufWriter::new(stdin);

while let Some(cmd) = write_rx.recv().await {
stdin_writer
.write_all((cmd + "\n").as_bytes())
.await
.expect("Bash command failed");

stdin_writer.flush().await.unwrap();
}
});

Self {
instance: RefCell::new(
Command::new("bash")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to start bash process"),
),
instance,
write_tx,
runtime,
}
}
}
Expand All @@ -46,34 +87,11 @@ impl Lang for BashLang {
rt: &mut Runtime,
cmd: String,
) -> shrs::anyhow::Result<CmdOutput> {
let mut instance = self.instance.borrow_mut();
let stdin = instance.stdin.as_mut().expect("Failed to open stdin");

let cd_statement = format!("cd {}\n", rt.working_dir.to_string_lossy());

stdin
.write_all(cd_statement.as_bytes())
.expect("unable to set var");

for (k, v) in rt.env.iter() {
let export_statement = format!("export {}={:?}\n", k, v);
stdin
.write_all(export_statement.as_bytes())
.expect("unable to set var");
}
stdin
.write_all((cmd + ";echo $?'\x1A'; echo '\x1A' >&2\n").as_bytes())
.expect("Bash command failed");

let stdout_reader =
BufReader::new(instance.stdout.as_mut().expect("Failed to open stdout"));
let status = read_out(ctx, stdout_reader)?;

let stderr_reader =
BufReader::new(instance.stderr.as_mut().expect("Failed to open stdout"));
read_err(ctx, stderr_reader)?;
self.runtime.block_on(async {
self.write_tx.send(cmd).await.unwrap();
});

Ok(CmdOutput::new(status))
Ok(CmdOutput::success())
}

fn name(&self) -> String {
Expand Down