From 4de0347fdc57363339b8faa869e8b2da28c550cf Mon Sep 17 00:00:00 2001 From: Klementiev Dmitry <109908018+klementievdmitry@users.noreply.github.com> Date: Wed, 15 Mar 2023 22:40:27 +0500 Subject: [PATCH] Added `help externs` command (#8403) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description `help externs` - command, which list external commands Closes https://github.com/nushell/nushell/issues/8301 # User-Facing Changes ```nu $ help externs ╭───┬──────────────┬─────────────┬───────────────────────────────────────────────────╮ │ # │ name │ module_name │ usage │ ├───┼──────────────┼─────────────┼───────────────────────────────────────────────────┤ │ 0 │ git push │ completions │ Push changes │ │ │ │ │ │ │ 1 │ git fetch │ completions │ Download objects and refs from another repository │ │ │ │ │ │ │ 2 │ git checkout │ completions │ Check out git branches and files │ │ │ │ │ │ ╰───┴──────────────┴─────────────┴───────────────────────────────────────────────────╯ ``` # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. --- .../src/core_commands/help_externs.rs | 152 ++++++++++++++++++ crates/nu-cmd-lang/src/core_commands/mod.rs | 2 + crates/nu-cmd-lang/src/default_context.rs | 1 + crates/nu-engine/src/scope.rs | 46 ++++++ 4 files changed, 201 insertions(+) create mode 100644 crates/nu-cmd-lang/src/core_commands/help_externs.rs diff --git a/crates/nu-cmd-lang/src/core_commands/help_externs.rs b/crates/nu-cmd-lang/src/core_commands/help_externs.rs new file mode 100644 index 0000000000..b465c44f24 --- /dev/null +++ b/crates/nu-cmd-lang/src/core_commands/help_externs.rs @@ -0,0 +1,152 @@ +use crate::help::highlight_search_in_table; +use nu_color_config::StyleComputer; +use nu_engine::{get_full_help, scope::ScopeData, CallExt}; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, + ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, +}; + +#[derive(Clone)] +pub struct HelpExterns; + +impl Command for HelpExterns { + fn name(&self) -> &str { + "help externs" + } + + fn usage(&self) -> &str { + "Show help on nushell externs." + } + + fn signature(&self) -> Signature { + Signature::build("help externs") + .category(Category::Core) + .rest( + "rest", + SyntaxShape::String, + "the name of extern to get help on", + ) + .named( + "find", + SyntaxShape::String, + "string to find in extern names and usage", + Some('f'), + ) + .input_output_types(vec![(Type::Nothing, Type::Table(vec![]))]) + .allow_variants_without_examples(true) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "show all externs", + example: "help externs", + result: None, + }, + Example { + description: "show help for single extern", + example: "help externs smth", + result: None, + }, + Example { + description: "search for string in extern names and usages", + example: "help externs --find smth", + result: None, + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + help_externs(engine_state, stack, call) + } +} + +pub fn help_externs( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, +) -> Result { + let head = call.head; + let find: Option> = call.get_flag(engine_state, stack, "find")?; + let rest: Vec> = call.rest(engine_state, stack, 0)?; + + // 🚩The following two-lines are copied from filters/find.rs: + let style_computer = StyleComputer::from_config(engine_state, stack); + // Currently, search results all use the same style. + // Also note that this sample string is passed into user-written code (the closure that may or may not be + // defined for "string"). + let string_style = style_computer.compute("string", &Value::string("search result", head)); + + if let Some(f) = find { + let all_cmds_vec = build_help_externs(engine_state, stack, head); + let found_cmds_vec = + highlight_search_in_table(all_cmds_vec, &f.item, &["name", "usage"], &string_style)?; + + return Ok(found_cmds_vec + .into_iter() + .into_pipeline_data(engine_state.ctrlc.clone())); + } + + if rest.is_empty() { + let found_cmds_vec = build_help_externs(engine_state, stack, head); + + Ok(found_cmds_vec + .into_iter() + .into_pipeline_data(engine_state.ctrlc.clone())) + } else { + let mut name = String::new(); + + for r in &rest { + if !name.is_empty() { + name.push(' '); + } + name.push_str(&r.item); + } + + let output = engine_state + .get_signatures_with_examples(false) + .iter() + .filter(|(signature, _, _, _, _)| signature.name == name) + .map(|(signature, examples, _, _, is_parser_keyword)| { + get_full_help(signature, examples, engine_state, stack, *is_parser_keyword) + }) + .collect::>(); + + if !output.is_empty() { + Ok(Value::String { + val: output.join("======================\n\n"), + span: call.head, + } + .into_pipeline_data()) + } else { + Err(ShellError::CommandNotFound(span(&[ + rest[0].span, + rest[rest.len() - 1].span, + ]))) + } + } +} + +fn build_help_externs(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec { + let mut scope = ScopeData::new(engine_state, stack); + scope.populate_all(); + scope.collect_externs(span) +} + +#[cfg(test)] +mod test { + #[test] + fn test_examples() { + use super::HelpExterns; + use crate::test_examples; + test_examples(HelpExterns {}) + } +} diff --git a/crates/nu-cmd-lang/src/core_commands/mod.rs b/crates/nu-cmd-lang/src/core_commands/mod.rs index 322a275cc7..c55d2812c6 100644 --- a/crates/nu-cmd-lang/src/core_commands/mod.rs +++ b/crates/nu-cmd-lang/src/core_commands/mod.rs @@ -20,6 +20,7 @@ mod for_; pub mod help; pub mod help_aliases; pub mod help_commands; +pub mod help_externs; pub mod help_modules; mod help_operators; mod hide; @@ -59,6 +60,7 @@ pub use for_::For; pub use help::Help; pub use help_aliases::HelpAliases; pub use help_commands::HelpCommands; +pub use help_externs::HelpExterns; pub use help_modules::HelpModules; pub use help_operators::HelpOperators; pub use hide::Hide; diff --git a/crates/nu-cmd-lang/src/default_context.rs b/crates/nu-cmd-lang/src/default_context.rs index 3b51ae3cfb..71809d634e 100644 --- a/crates/nu-cmd-lang/src/default_context.rs +++ b/crates/nu-cmd-lang/src/default_context.rs @@ -39,6 +39,7 @@ pub fn create_default_context() -> EngineState { HelpAliases, HelpCommands, HelpModules, + HelpExterns, HelpOperators, Hide, HideEnv, diff --git a/crates/nu-engine/src/scope.rs b/crates/nu-engine/src/scope.rs index 2b57a5bf62..34af6b2906 100644 --- a/crates/nu-engine/src/scope.rs +++ b/crates/nu-engine/src/scope.rs @@ -463,6 +463,52 @@ impl<'e, 's> ScopeData<'e, 's> { sig_records } + pub fn collect_externs(&self, span: Span) -> Vec { + let mut externals = vec![]; + for ((command_name, _), decl_id) in &self.commands_map { + let decl = self.engine_state.get_decl(**decl_id); + + if decl.is_known_external() { + let mut cols = vec![]; + let mut vals = vec![]; + + let mut module_commands = vec![]; + for module in &self.modules_map { + let module_name = String::from_utf8_lossy(module.0).to_string(); + let module_id = self.engine_state.find_module(module.0, &[]); + if let Some(module_id) = module_id { + let module = self.engine_state.get_module(module_id); + if module.has_decl(command_name) { + module_commands.push(module_name); + } + } + } + + cols.push("name".into()); + vals.push(Value::String { + val: String::from_utf8_lossy(command_name).to_string(), + span, + }); + + cols.push("module_name".into()); + vals.push(Value::String { + val: module_commands.join(", "), + span, + }); + + cols.push("usage".to_string()); + vals.push(Value::String { + val: decl.usage().into(), + span, + }); + + externals.push(Value::Record { cols, vals, span }) + } + } + + externals + } + pub fn collect_aliases(&self, span: Span) -> Vec { let mut aliases = vec![]; for (alias_name, alias_id) in &self.aliases_map {