nu_command/system/
which_.rs

1use nu_engine::{command_prelude::*, env};
2use nu_protocol::engine::CommandType;
3use std::{ffi::OsStr, path::Path};
4
5#[derive(Clone)]
6pub struct Which;
7
8impl Command for Which {
9    fn name(&self) -> &str {
10        "which"
11    }
12
13    fn signature(&self) -> Signature {
14        Signature::build("which")
15            .input_output_types(vec![(Type::Nothing, Type::table())])
16            .allow_variants_without_examples(true)
17            .rest("applications", SyntaxShape::String, "Application(s).")
18            .switch("all", "list all executables", Some('a'))
19            .category(Category::System)
20    }
21
22    fn description(&self) -> &str {
23        "Finds a program file, alias or custom command."
24    }
25
26    fn search_terms(&self) -> Vec<&str> {
27        vec![
28            "find",
29            "path",
30            "location",
31            "command",
32            "whereis",     // linux binary to find binary locations in path
33            "get-command", // powershell command to find commands and binaries in path
34        ]
35    }
36
37    fn run(
38        &self,
39        engine_state: &EngineState,
40        stack: &mut Stack,
41        call: &Call,
42        _input: PipelineData,
43    ) -> Result<PipelineData, ShellError> {
44        which(engine_state, stack, call)
45    }
46
47    fn examples(&self) -> Vec<Example> {
48        vec![Example {
49            description: "Find if the 'myapp' application is available",
50            example: "which myapp",
51            result: None,
52        }]
53    }
54}
55
56// Shortcut for creating an entry to the output table
57fn entry(
58    arg: impl Into<String>,
59    path: impl Into<String>,
60    cmd_type: CommandType,
61    span: Span,
62) -> Value {
63    Value::record(
64        record! {
65            "command" => Value::string(arg, span),
66            "path" => Value::string(path, span),
67            "type" => Value::string(cmd_type.to_string(), span),
68        },
69        span,
70    )
71}
72
73fn get_entry_in_commands(engine_state: &EngineState, name: &str, span: Span) -> Option<Value> {
74    if let Some(decl_id) = engine_state.find_decl(name.as_bytes(), &[]) {
75        let decl = engine_state.get_decl(decl_id);
76        Some(entry(name, "", decl.command_type(), span))
77    } else {
78        None
79    }
80}
81
82fn get_entries_in_nu(
83    engine_state: &EngineState,
84    name: &str,
85    span: Span,
86    skip_after_first_found: bool,
87) -> Vec<Value> {
88    let mut all_entries = vec![];
89
90    if !all_entries.is_empty() && skip_after_first_found {
91        return all_entries;
92    }
93
94    if let Some(ent) = get_entry_in_commands(engine_state, name, span) {
95        all_entries.push(ent);
96    }
97
98    all_entries
99}
100
101fn get_first_entry_in_path(
102    item: &str,
103    span: Span,
104    cwd: impl AsRef<Path>,
105    paths: impl AsRef<OsStr>,
106) -> Option<Value> {
107    which::which_in(item, Some(paths), cwd)
108        .map(|path| entry(item, path.to_string_lossy(), CommandType::External, span))
109        .ok()
110}
111
112fn get_all_entries_in_path(
113    item: &str,
114    span: Span,
115    cwd: impl AsRef<Path>,
116    paths: impl AsRef<OsStr>,
117) -> Vec<Value> {
118    which::which_in_all(&item, Some(paths), cwd)
119        .map(|iter| {
120            iter.map(|path| entry(item, path.to_string_lossy(), CommandType::External, span))
121                .collect()
122        })
123        .unwrap_or_default()
124}
125
126#[derive(Debug)]
127struct WhichArgs {
128    applications: Vec<Spanned<String>>,
129    all: bool,
130}
131
132fn which_single(
133    application: Spanned<String>,
134    all: bool,
135    engine_state: &EngineState,
136    cwd: impl AsRef<Path>,
137    paths: impl AsRef<OsStr>,
138) -> Vec<Value> {
139    let (external, prog_name) = if application.item.starts_with('^') {
140        (true, application.item[1..].to_string())
141    } else {
142        (false, application.item.clone())
143    };
144
145    //If prog_name is an external command, don't search for nu-specific programs
146    //If all is false, we can save some time by only searching for the first matching
147    //program
148    //This match handles all different cases
149    match (all, external) {
150        (true, true) => get_all_entries_in_path(&prog_name, application.span, cwd, paths),
151        (true, false) => {
152            let mut output: Vec<Value> = vec![];
153            output.extend(get_entries_in_nu(
154                engine_state,
155                &prog_name,
156                application.span,
157                false,
158            ));
159            output.extend(get_all_entries_in_path(
160                &prog_name,
161                application.span,
162                cwd,
163                paths,
164            ));
165            output
166        }
167        (false, true) => {
168            if let Some(entry) = get_first_entry_in_path(&prog_name, application.span, cwd, paths) {
169                return vec![entry];
170            }
171            vec![]
172        }
173        (false, false) => {
174            let nu_entries = get_entries_in_nu(engine_state, &prog_name, application.span, true);
175            if !nu_entries.is_empty() {
176                return vec![nu_entries[0].clone()];
177            } else if let Some(entry) =
178                get_first_entry_in_path(&prog_name, application.span, cwd, paths)
179            {
180                return vec![entry];
181            }
182            vec![]
183        }
184    }
185}
186
187fn which(
188    engine_state: &EngineState,
189    stack: &mut Stack,
190    call: &Call,
191) -> Result<PipelineData, ShellError> {
192    let head = call.head;
193    let which_args = WhichArgs {
194        applications: call.rest(engine_state, stack, 0)?,
195        all: call.has_flag(engine_state, stack, "all")?,
196    };
197
198    if which_args.applications.is_empty() {
199        return Err(ShellError::MissingParameter {
200            param_name: "application".into(),
201            span: head,
202        });
203    }
204
205    let mut output = vec![];
206
207    #[allow(deprecated)]
208    let cwd = env::current_dir_str(engine_state, stack)?;
209    let paths = env::path_str(engine_state, stack, head)?;
210
211    for app in which_args.applications {
212        let values = which_single(
213            app,
214            which_args.all,
215            engine_state,
216            cwd.clone(),
217            paths.clone(),
218        );
219        output.extend(values);
220    }
221
222    Ok(output
223        .into_iter()
224        .into_pipeline_data(head, engine_state.signals().clone()))
225}
226
227#[cfg(test)]
228mod test {
229    use super::*;
230
231    #[test]
232    fn test_examples() {
233        crate::test_examples(Which)
234    }
235}