Skip to main content

command_stream/commands/
which.rs

1//! Virtual `which` command implementation
2
3use crate::commands::CommandContext;
4use crate::utils::{CommandResult, VirtualUtils};
5
6/// List of virtual (shell builtin) commands
7const 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
12/// Execute the which command
13///
14/// Locates commands in the PATH or identifies shell builtins.
15pub 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        // Check if it's a virtual/builtin command
30        if VIRTUAL_COMMANDS.contains(&cmd.as_str()) {
31            output.push_str(&format!("{}: shell builtin\n", cmd));
32        } else {
33            // Try to find in PATH
34            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        // Some commands found, some not
54        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        // 'sh' should exist on most Unix systems
71        let ctx = CommandContext::new(vec!["sh".to_string()]);
72        let result = which(ctx).await;
73
74        // May or may not find sh depending on PATH
75        // Just check it doesn't panic
76        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}