use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value, }; use nu_engine::{get_full_help, CallExt}; use std::borrow::Borrow; #[derive(Clone)] pub struct Help; impl Command for Help { fn name(&self) -> &str { "help" } fn signature(&self) -> Signature { Signature::build("help") .rest( "rest", SyntaxShape::String, "the name of command to get help on", ) .named( "find", SyntaxShape::String, "string to find in command names, usage, and search terms", Some('f'), ) .category(Category::Core) } fn usage(&self) -> &str { "Display help information about commands." } fn run( &self, engine_state: &EngineState, stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { help(engine_state, stack, call) } fn examples(&self) -> Vec { vec![ Example { description: "show all commands and sub-commands", example: "help commands", result: None, }, Example { description: "show help for single command", example: "help match", result: None, }, Example { description: "show help for single sub-command", example: "help str lpad", result: None, }, Example { description: "search for string in command names, usage and search terms", example: "help --find char", result: None, }, ] } } fn help( 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)?; let commands = engine_state.get_decl_ids_sorted(false); if let Some(f) = find { let search_string = f.item.to_lowercase(); let mut found_cmds_vec = Vec::new(); for decl_id in commands { let mut cols = vec![]; let mut vals = vec![]; let decl = engine_state.get_decl(decl_id); let sig = decl.signature().update_from_command(decl.borrow()); let key = sig.name; let usage = sig.usage; let search_terms = sig.search_terms; let matches_term = if !search_terms.is_empty() { search_terms .iter() .any(|term| term.to_lowercase().contains(&search_string)) } else { false }; if key.to_lowercase().contains(&search_string) || usage.to_lowercase().contains(&search_string) || matches_term { cols.push("name".into()); vals.push(Value::String { val: key, span: head, }); cols.push("category".into()); vals.push(Value::String { val: sig.category.to_string(), span: head, }); cols.push("is_plugin".into()); vals.push(Value::Bool { val: decl.is_plugin().is_some(), span: head, }); cols.push("is_custom".into()); vals.push(Value::Bool { val: decl.get_block_id().is_some(), span: head, }); cols.push("is_keyword".into()); vals.push(Value::Bool { val: decl.is_parser_keyword(), span: head, }); cols.push("usage".into()); vals.push(Value::String { val: usage, span: head, }); cols.push("search_terms".into()); vals.push(if search_terms.is_empty() { Value::nothing(head) } else { Value::String { val: search_terms.join(", "), span: head, } }); found_cmds_vec.push(Value::Record { cols, vals, span: head, }); } } return Ok(found_cmds_vec .into_iter() .into_pipeline_data(engine_state.ctrlc.clone())); } if !rest.is_empty() { let mut found_cmds_vec = Vec::new(); if rest[0].item == "commands" { for decl_id in commands { let mut cols = vec![]; let mut vals = vec![]; let decl = engine_state.get_decl(decl_id); let sig = decl.signature().update_from_command(decl.borrow()); let key = sig.name; let usage = sig.usage; let search_terms = sig.search_terms; cols.push("name".into()); vals.push(Value::String { val: key, span: head, }); cols.push("category".into()); vals.push(Value::String { val: sig.category.to_string(), span: head, }); cols.push("is_plugin".into()); vals.push(Value::Bool { val: decl.is_plugin().is_some(), span: head, }); cols.push("is_custom".into()); vals.push(Value::Bool { val: decl.get_block_id().is_some(), span: head, }); cols.push("is_keyword".into()); vals.push(Value::Bool { val: decl.is_parser_keyword(), span: head, }); cols.push("usage".into()); vals.push(Value::String { val: usage, span: head, }); cols.push("search_terms".into()); vals.push(if search_terms.is_empty() { Value::nothing(head) } else { Value::String { val: search_terms.join(", "), span: head, } }); found_cmds_vec.push(Value::Record { cols, vals, span: 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, _, _)| { get_full_help(signature, examples, engine_state, stack) }) .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, ]))) } } } else { let msg = r#"Welcome to Nushell. Here are some tips to help you get started. * help commands - list all available commands * help - display help about a particular command * help --find - search through all of help Nushell works on the idea of a "pipeline". Pipelines are commands connected with the '|' character. Each stage in the pipeline works together to load, parse, and display information to you. [Examples] List the files in the current directory, sorted by size: ls | sort-by size Get information about the current system: sys | get host Get the processes on your system actively using CPU: ps | where cpu > 0 You can also learn more at https://www.nushell.sh/book/"#; Ok(Value::String { val: msg.into(), span: head, } .into_pipeline_data()) } }