use super::shell_provider::{ShellError, ShellExecCommand};
use super::shell_tool_utils::ShellType;
use crate::constants::env::{ai, system};
use std::collections::HashMap;
fn get_disable_extglob_command(shell_path: &str) -> Option<String> {
if std::env::var(ai::SHELL_PREFIX).is_ok() {
return Some(
"{ shopt -u extglob || setopt NO_EXTENDED_GLOB; } >/dev/null 2>&1 || true".to_string(),
);
}
if shell_path.contains("bash") {
Some("shopt -u extglob 2>/dev/null || true".to_string())
} else if shell_path.contains("zsh") {
Some("setopt NO_EXTENDED_GLOB 2>/dev/null || true".to_string())
} else {
None
}
}
pub struct BashShellProvider {
shell_path: String,
}
impl BashShellProvider {
pub fn new(shell_path: &str) -> Self {
Self {
shell_path: shell_path.to_string(),
}
}
pub fn get_type(&self) -> ShellType {
ShellType::Bash
}
pub fn get_shell_path(&self) -> &str {
&self.shell_path
}
pub fn is_detached(&self) -> bool {
true
}
pub async fn build_exec_command(
&self,
command: &str,
id: usize,
sandbox_tmp_dir: Option<&str>,
use_sandbox: bool,
) -> Result<ShellExecCommand, ShellError> {
let tmpdir = std::env::temp_dir();
let (shell_cwd_file_path, cwd_file_path) = if use_sandbox {
let sandbox = sandbox_tmp_dir.ok_or_else(|| {
ShellError::BuildError(
"sandbox_tmp_dir required when use_sandbox is true".to_string(),
)
})?;
(
format!("{}/cwd-{}", sandbox, id),
format!("{}/cwd-{}", sandbox, id),
)
} else {
let cwd_file = format!("ai-{}-cwd", id);
(
tmpdir.join(&cwd_file).to_string_lossy().to_string(),
tmpdir.join(&cwd_file).to_string_lossy().to_string(),
)
};
let mut command_parts: Vec<String> = Vec::new();
if let Some(disable_extglob_cmd) = get_disable_extglob_command(&self.shell_path) {
command_parts.push(disable_extglob_cmd);
}
command_parts.push(format!("eval {}", self.quote_command(command)));
command_parts.push(format!(
"pwd -P >| {}",
self.quote_path(&shell_cwd_file_path)
));
let command_string = command_parts.join(" && ");
let command_string = if let Ok(prefix) = std::env::var(ai::SHELL_PREFIX) {
format!("{} -c '{}'", prefix, command_string)
} else {
command_string
};
Ok(ShellExecCommand {
command_string,
cwd_file_path,
})
}
pub fn get_spawn_args(&self, command_string: &str) -> Vec<String> {
vec![
"-c".to_string(),
"-l".to_string(),
command_string.to_string(),
]
}
pub async fn get_environment_overrides(&self, _command: &str) -> HashMap<String, String> {
let mut env = HashMap::new();
env
}
fn quote_command(&self, command: &str) -> String {
format!("'{}'", command.replace('\'', "'\\''"))
}
fn quote_path(&self, path: &str) -> String {
self.quote_command(path)
}
}
impl Default for BashShellProvider {
fn default() -> Self {
#[cfg(target_os = "windows")]
let shell = "bash".to_string();
#[cfg(not(target_os = "windows"))]
let shell = std::env::var(system::SHELL).unwrap_or_else(|_| "/bin/bash".to_string());
Self::new(&shell)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_build_exec_command() {
let provider = BashShellProvider::new("/bin/bash");
let result = provider
.build_exec_command("echo hello", 1, None, false)
.await;
assert!(result.is_ok());
let cmd = result.unwrap();
assert!(cmd.command_string.contains("echo hello"));
}
#[test]
fn test_get_spawn_args() {
let provider = BashShellProvider::new("/bin/bash");
let args = provider.get_spawn_args("echo hello");
assert_eq!(args[0], "-c");
}
#[test]
fn test_disable_extglob_command() {
let cmd = get_disable_extglob_command("/bin/bash");
assert!(cmd.is_some());
let cmd = get_disable_extglob_command("/bin/zsh");
assert!(cmd.is_some());
let cmd = get_disable_extglob_command("/bin/sh");
assert!(cmd.is_none());
}
}