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", "tee", "test", "head", "tail",
10    "sort", "uniq",
11];
12
13/// Execute the which command
14///
15/// Locates commands in the PATH or identifies shell builtins.
16pub 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        // Check if it's a virtual/builtin command
31        if VIRTUAL_COMMANDS.contains(&cmd.as_str()) {
32            output.push_str(&format!("{}: shell builtin\n", cmd));
33        } else {
34            // Try to find in PATH
35            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        // Some commands found, some not
51        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        // 'sh' should exist on most Unix systems
64        let ctx = CommandContext::new(vec!["sh".to_string()]);
65        let result = which(ctx).await;
66
67        // May or may not find sh depending on PATH
68        // Just check it doesn't panic
69        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}