1use itertools::Itertools;
2use nu_engine::{command_prelude::*, env};
3use nu_protocol::engine::CommandType;
4use std::fs;
5use std::{ffi::OsStr, path::Path};
6use which::sys;
7use which::sys::Sys;
8
9#[derive(Clone)]
10pub struct Which;
11
12impl Command for Which {
13 fn name(&self) -> &str {
14 "which"
15 }
16
17 fn signature(&self) -> Signature {
18 Signature::build("which")
19 .input_output_types(vec![(Type::Nothing, Type::table())])
20 .allow_variants_without_examples(true)
21 .rest("applications", SyntaxShape::String, "Application(s).")
22 .switch("all", "list all executables", Some('a'))
23 .category(Category::System)
24 }
25
26 fn description(&self) -> &str {
27 "Finds a program file, alias or custom command. If `application` is not provided, all deduplicated commands will be returned."
28 }
29
30 fn search_terms(&self) -> Vec<&str> {
31 vec![
32 "find",
33 "path",
34 "location",
35 "command",
36 "whereis", "get-command", ]
39 }
40
41 fn run(
42 &self,
43 engine_state: &EngineState,
44 stack: &mut Stack,
45 call: &Call,
46 _input: PipelineData,
47 ) -> Result<PipelineData, ShellError> {
48 which(engine_state, stack, call)
49 }
50
51 fn examples(&self) -> Vec<Example<'_>> {
52 vec![
53 Example {
54 description: "Find if the 'myapp' application is available",
55 example: "which myapp",
56 result: None,
57 },
58 Example {
59 description: "Find all executables across all paths without deduplication",
60 example: "which -a",
61 result: None,
62 },
63 ]
64 }
65}
66
67fn entry(
69 arg: impl Into<String>,
70 path: impl Into<String>,
71 cmd_type: CommandType,
72 span: Span,
73) -> Value {
74 Value::record(
75 record! {
76 "command" => Value::string(arg, span),
77 "path" => Value::string(path, span),
78 "type" => Value::string(cmd_type.to_string(), span),
79 },
80 span,
81 )
82}
83
84fn get_entry_in_commands(engine_state: &EngineState, name: &str, span: Span) -> Option<Value> {
85 if let Some(decl_id) = engine_state.find_decl(name.as_bytes(), &[]) {
86 let decl = engine_state.get_decl(decl_id);
87 Some(entry(name, "", decl.command_type(), span))
88 } else {
89 None
90 }
91}
92
93fn get_first_entry_in_path(
94 item: &str,
95 span: Span,
96 cwd: impl AsRef<Path>,
97 paths: impl AsRef<OsStr>,
98) -> Option<Value> {
99 which::which_in(item, Some(paths), cwd)
100 .map(|path| entry(item, path.to_string_lossy(), CommandType::External, span))
101 .ok()
102}
103
104fn get_all_entries_in_path(
105 item: &str,
106 span: Span,
107 cwd: impl AsRef<Path>,
108 paths: impl AsRef<OsStr>,
109) -> Vec<Value> {
110 which::which_in_all(&item, Some(paths), cwd)
111 .map(|iter| {
112 iter.map(|path| entry(item, path.to_string_lossy(), CommandType::External, span))
113 .collect()
114 })
115 .unwrap_or_default()
116}
117
118fn list_all_executables(
119 engine_state: &EngineState,
120 paths: impl AsRef<OsStr>,
121 all: bool,
122) -> Vec<Value> {
123 let decls = engine_state.get_decls_sorted(false);
124 let commands = decls
125 .into_iter()
126 .map(|x| {
127 let decl = engine_state.get_decl(x.1);
128 (
129 String::from_utf8_lossy(&x.0).to_string(),
130 String::new(),
131 decl.command_type(),
132 )
133 })
134 .chain(
135 sys::RealSys
136 .env_split_paths(paths.as_ref())
137 .into_iter()
138 .filter_map(|dir| fs::read_dir(dir).ok())
139 .flat_map(|entries| entries.flatten())
140 .map(|entry| entry.path())
141 .filter(|path| path.is_file())
142 .filter_map(|path| {
143 let filename = path.file_name()?.to_string_lossy().to_string();
144 Some((
145 filename,
146 path.to_string_lossy().to_string(),
147 CommandType::External,
148 ))
149 }),
150 );
151
152 if all {
153 commands
154 .map(|(filename, path, cmd_type)| entry(filename, path, cmd_type, Span::new(0, 0)))
155 .collect()
156 } else {
157 commands
158 .unique_by(|x| x.0.clone())
159 .map(|(filename, path, cmd_type)| entry(filename, path, cmd_type, Span::new(0, 0)))
160 .collect()
161 }
162}
163
164#[derive(Debug)]
165struct WhichArgs {
166 applications: Vec<Spanned<String>>,
167 all: bool,
168}
169
170fn which_single(
171 application: Spanned<String>,
172 all: bool,
173 engine_state: &EngineState,
174 cwd: impl AsRef<Path>,
175 paths: impl AsRef<OsStr>,
176) -> Vec<Value> {
177 let (external, prog_name) = if application.item.starts_with('^') {
178 (true, application.item[1..].to_string())
179 } else {
180 (false, application.item.clone())
181 };
182
183 match (all, external) {
188 (true, true) => get_all_entries_in_path(&prog_name, application.span, cwd, paths),
189 (true, false) => {
190 let mut output: Vec<Value> = vec![];
191 if let Some(entry) = get_entry_in_commands(engine_state, &prog_name, application.span) {
192 output.push(entry);
193 }
194 output.extend(get_all_entries_in_path(
195 &prog_name,
196 application.span,
197 cwd,
198 paths,
199 ));
200 output
201 }
202 (false, true) => get_first_entry_in_path(&prog_name, application.span, cwd, paths)
203 .into_iter()
204 .collect(),
205 (false, false) => get_entry_in_commands(engine_state, &prog_name, application.span)
206 .or_else(|| get_first_entry_in_path(&prog_name, application.span, cwd, paths))
207 .into_iter()
208 .collect(),
209 }
210}
211
212fn which(
213 engine_state: &EngineState,
214 stack: &mut Stack,
215 call: &Call,
216) -> Result<PipelineData, ShellError> {
217 let head = call.head;
218 let which_args = WhichArgs {
219 applications: call.rest(engine_state, stack, 0)?,
220 all: call.has_flag(engine_state, stack, "all")?,
221 };
222
223 let mut output = vec![];
224
225 #[allow(deprecated)]
226 let cwd = env::current_dir_str(engine_state, stack)?;
227 let paths = env::path_str(engine_state, stack, head)?;
228
229 if which_args.applications.is_empty() {
230 return Ok(list_all_executables(engine_state, paths, which_args.all)
231 .into_iter()
232 .into_pipeline_data(head, engine_state.signals().clone()));
233 }
234
235 for app in which_args.applications {
236 let values = which_single(
237 app,
238 which_args.all,
239 engine_state,
240 cwd.clone(),
241 paths.clone(),
242 );
243 output.extend(values);
244 }
245
246 Ok(output
247 .into_iter()
248 .into_pipeline_data(head, engine_state.signals().clone()))
249}
250
251#[cfg(test)]
252mod test {
253 use super::*;
254
255 #[test]
256 fn test_examples() {
257 crate::test_examples(Which)
258 }
259}