command_stream/commands/
which.rs1use crate::commands::CommandContext;
4use crate::utils::{CommandResult, VirtualUtils};
5
6const VIRTUAL_COMMANDS: &[&str] = &[
8 "echo", "pwd", "cd", "true", "false", "sleep", "cat", "ls", "mkdir", "rm", "touch", "cp", "mv",
9 "basename", "dirname", "env", "exit", "which", "yes", "seq", "tee", "test", "head", "tail",
10 "sort", "uniq",
11];
12
13pub async fn which(ctx: CommandContext) -> CommandResult {
17 if ctx.args.is_empty() {
18 return VirtualUtils::missing_operand_error("which");
19 }
20
21 let mut output = String::new();
22 let mut errors = String::new();
23 let mut found_all = true;
24
25 for cmd in &ctx.args {
26 if cmd.starts_with('-') {
27 continue;
28 }
29
30 if VIRTUAL_COMMANDS.contains(&cmd.as_str()) {
32 output.push_str(&format!("{}: shell builtin\n", cmd));
33 } else {
34 match which::which(cmd) {
36 Ok(path) => {
37 output.push_str(&format!("{}\n", path.display()));
38 }
39 Err(_) => {
40 found_all = false;
41 errors.push_str(&format!("which: no {} in PATH\n", cmd));
42 }
43 }
44 }
45 }
46
47 if output.is_empty() {
48 CommandResult::new("", errors, 1)
49 } else if !found_all {
50 CommandResult::new(output, errors, 1)
52 } else {
53 CommandResult::success(output)
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[tokio::test]
62 async fn test_which_existing_command() {
63 let ctx = CommandContext::new(vec!["sh".to_string()]);
65 let result = which(ctx).await;
66
67 assert!(result.code == 0 || result.code == 1);
70 }
71
72 #[tokio::test]
73 async fn test_which_nonexistent_command() {
74 let ctx = CommandContext::new(vec!["nonexistent_command_12345".to_string()]);
75 let result = which(ctx).await;
76
77 assert_eq!(result.code, 1);
78 }
79}