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", "test",
10];
11
12pub async fn which(ctx: CommandContext) -> CommandResult {
16 if ctx.args.is_empty() {
17 return VirtualUtils::missing_operand_error("which");
18 }
19
20 let mut output = String::new();
21 let mut errors = String::new();
22 let mut found_all = true;
23
24 for cmd in &ctx.args {
25 if cmd.starts_with('-') {
26 continue;
27 }
28
29 if VIRTUAL_COMMANDS.contains(&cmd.as_str()) {
31 output.push_str(&format!("{}: shell builtin\n", cmd));
32 } else {
33 match which::which(cmd) {
35 Ok(path) => {
36 output.push_str(&format!("{}\n", path.display()));
37 }
38 Err(_) => {
39 found_all = false;
40 errors.push_str(&format!("which: no {} in PATH\n", cmd));
41 }
42 }
43 }
44 }
45
46 if output.is_empty() {
47 CommandResult {
48 stdout: String::new(),
49 stderr: errors,
50 code: 1,
51 }
52 } else if !found_all {
53 CommandResult {
55 stdout: output,
56 stderr: errors,
57 code: 1,
58 }
59 } else {
60 CommandResult::success(output)
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[tokio::test]
69 async fn test_which_existing_command() {
70 let ctx = CommandContext::new(vec!["sh".to_string()]);
72 let result = which(ctx).await;
73
74 assert!(result.code == 0 || result.code == 1);
77 }
78
79 #[tokio::test]
80 async fn test_which_nonexistent_command() {
81 let ctx = CommandContext::new(vec!["nonexistent_command_12345".to_string()]);
82 let result = which(ctx).await;
83
84 assert_eq!(result.code, 1);
85 }
86}