use crate::commands::WholeStreamCommand; use crate::prelude::*; use indexmap::map::IndexMap; use log::trace; use nu_errors::ShellError; use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_source::Tagged; pub struct Which; #[async_trait] impl WholeStreamCommand for Which { fn name(&self) -> &str { "which" } fn signature(&self) -> Signature { Signature::build("which") .required("application", SyntaxShape::String, "application") .switch("all", "list all executables", Some('a')) } fn usage(&self) -> &str { "Finds a program file, alias or custom command." } async fn run(&self, args: CommandArgs) -> Result { which(args).await } } /// Shortcuts for creating an entry to the output table fn entry(arg: impl Into, path: Value, builtin: bool, tag: Tag) -> Value { let mut map = IndexMap::new(); map.insert( "arg".to_string(), UntaggedValue::Primitive(Primitive::String(arg.into())).into_value(tag.clone()), ); map.insert("path".to_string(), path); map.insert( "builtin".to_string(), UntaggedValue::boolean(builtin).into_value(tag.clone()), ); UntaggedValue::row(map).into_value(tag) } macro_rules! create_entry { ($arg:expr, $path:expr, $tag:expr, $is_builtin:expr) => { entry( $arg.clone(), UntaggedValue::Primitive(Primitive::String($path.to_string())).into_value($tag.clone()), $is_builtin, $tag, ) }; } fn get_entries_in_aliases(scope: &Scope, name: &str, tag: Tag) -> Vec { let aliases = scope .get_aliases_with_name(name) .unwrap_or_default() .into_iter() .map(|_| create_entry!(name, "Nushell alias", tag.clone(), false)) .collect::>(); trace!("Found {} aliases", aliases.len()); aliases } fn get_entries_in_custom_command(scope: &Scope, name: &str, tag: Tag) -> Vec { scope .get_custom_commands_with_name(name) .unwrap_or_default() .into_iter() .map(|_| create_entry!(name, "Nushell custom command", tag.clone(), false)) .collect() } fn get_entry_in_commands(scope: &Scope, name: &str, tag: Tag) -> Option { if scope.has_command(name) { Some(create_entry!(name, "Nushell built-in command", tag, true)) } else { None } } fn get_entries_in_nu( scope: &Scope, name: &str, tag: Tag, skip_after_first_found: bool, ) -> Vec { let mut all_entries = vec![]; all_entries.extend(get_entries_in_aliases(scope, name, tag.clone())); if !all_entries.is_empty() && skip_after_first_found { return all_entries; } all_entries.extend(get_entries_in_custom_command(scope, name, tag.clone())); if !all_entries.is_empty() && skip_after_first_found { return all_entries; } if let Some(entry) = get_entry_in_commands(scope, name, tag) { all_entries.push(entry); } all_entries } #[allow(unused)] macro_rules! entry_path { ($arg:expr, $path:expr, $tag:expr) => { entry( $arg.clone(), UntaggedValue::Primitive(Primitive::FilePath($path)).into_value($tag.clone()), false, $tag, ) }; } #[cfg(feature = "ichwh")] async fn get_first_entry_in_path(item: &str, tag: Tag) -> Option { ichwh::which(item) .await .unwrap_or(None) .map(|path| entry_path!(item, path.into(), tag)) } #[cfg(not(feature = "ichwh"))] async fn get_first_entry_in_path(_: &str, _: Tag) -> Option { None } #[cfg(feature = "ichwh")] async fn get_all_entries_in_path(item: &str, tag: Tag) -> Vec { ichwh::which_all(&item) .await .unwrap_or_default() .into_iter() .map(|path| entry_path!(item, path.into(), tag.clone())) .collect() } #[cfg(not(feature = "ichwh"))] async fn get_all_entries_in_path(_: &str, _: Tag) -> Vec { vec![] } #[derive(Deserialize, Debug)] struct WhichArgs { application: Tagged, all: bool, } async fn which(args: CommandArgs) -> Result { let scope = args.scope.clone(); let (WhichArgs { application, all }, _) = args.process().await?; let (external, prog_name) = if application.starts_with('^') { (true, application.item[1..].to_string()) } else { (false, application.item.clone()) }; let mut output = vec![]; //If prog_name is an external command, don't search for nu-specific programs //If all is false, we can save some time by only searching for the first matching //program //This match handles all different cases match (all, external) { (true, true) => { output.extend(get_all_entries_in_path(&prog_name, application.tag.clone()).await); } (true, false) => { output.extend(get_entries_in_nu( &scope, &prog_name, application.tag.clone(), false, )); output.extend(get_all_entries_in_path(&prog_name, application.tag.clone()).await); } (false, true) => { if let Some(entry) = get_first_entry_in_path(&prog_name, application.tag.clone()).await { output.push(entry); } } (false, false) => { let nu_entries = get_entries_in_nu(&scope, &prog_name, application.tag.clone(), true); if !nu_entries.is_empty() { output.push(nu_entries[0].clone()); } else if let Some(entry) = get_first_entry_in_path(&prog_name, application.tag.clone()).await { output.push(entry); } } } Ok(futures::stream::iter(output.into_iter().map(ReturnSuccess::value)).to_output_stream()) } #[cfg(test)] mod tests { use super::ShellError; use super::Which; #[test] fn examples_work_as_expected() -> Result<(), ShellError> { use crate::examples::test as test_examples; Ok(test_examples(Which {})?) } }