81 lines
2.1 KiB
Rust
81 lines
2.1 KiB
Rust
use std::env;
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
/// Execute a shell command line and print output directly to stdout/stderr.
|
|
/// Returns the exit code.
|
|
pub fn execute(input: &str) -> i32 {
|
|
let trimmed = input.trim();
|
|
|
|
// Handle cd specially - it must affect our process
|
|
if trimmed == "cd" || trimmed.starts_with("cd ") {
|
|
return handle_cd(trimmed);
|
|
}
|
|
|
|
// Run everything else through sh -c for full shell semantics
|
|
// (pipes, redirects, globs, etc.)
|
|
let status = Command::new("sh")
|
|
.arg("-c")
|
|
.arg(trimmed)
|
|
.status();
|
|
|
|
match status {
|
|
Ok(s) => s.code().unwrap_or(1),
|
|
Err(e) => {
|
|
eprintln!("aishell: {}", e);
|
|
127
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_cd(input: &str) -> i32 {
|
|
let target = input.strip_prefix("cd").unwrap().trim();
|
|
|
|
let dir: PathBuf = if target.is_empty() {
|
|
// cd with no args → home directory
|
|
match env::var("HOME") {
|
|
Ok(home) => PathBuf::from(home),
|
|
Err(_) => {
|
|
eprintln!("aishell: cd: HOME not set");
|
|
return 1;
|
|
}
|
|
}
|
|
} else if target.starts_with('~') {
|
|
// Expand ~ to HOME
|
|
match env::var("HOME") {
|
|
Ok(home) => PathBuf::from(target.replacen('~', &home, 1)),
|
|
Err(_) => {
|
|
eprintln!("aishell: cd: HOME not set");
|
|
return 1;
|
|
}
|
|
}
|
|
} else if target == "-" {
|
|
// cd - → previous directory
|
|
match env::var("OLDPWD") {
|
|
Ok(old) => {
|
|
println!("{}", old);
|
|
PathBuf::from(old)
|
|
}
|
|
Err(_) => {
|
|
eprintln!("aishell: cd: OLDPWD not set");
|
|
return 1;
|
|
}
|
|
}
|
|
} else {
|
|
PathBuf::from(target)
|
|
};
|
|
|
|
// Save current dir as OLDPWD
|
|
if let Ok(cwd) = env::current_dir() {
|
|
env::set_var("OLDPWD", cwd);
|
|
}
|
|
|
|
match env::set_current_dir(&dir) {
|
|
Ok(_) => 0,
|
|
Err(e) => {
|
|
eprintln!("aishell: cd: {}: {}", dir.display(), e);
|
|
1
|
|
}
|
|
}
|
|
}
|