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", "get-command", ]
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
56fn 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 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}