use nu_protocol::{ ast::Call, engine::{Command, EvaluationContext}, Example, ShellError, Signature, Spanned, SyntaxShape, Value, }; use nu_engine::CallExt; 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 usage", Some('f'), ) } fn usage(&self) -> &str { "Display help information about commands." } fn run( &self, context: &EvaluationContext, call: &Call, _input: Value, ) -> Result { help(context, call) } fn examples(&self) -> Vec { vec![ Example { description: "show all commands and sub-commands", example: "help commands", result: None, }, Example { description: "generate documentation", example: "help generate_docs", 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 usage", example: "help --find char", result: None, }, ] } } fn help(context: &EvaluationContext, call: &Call) -> Result { let span = call.head; let find: Option> = call.get_flag(context, "find")?; let rest: Vec> = call.rest(context, 0)?; let full_commands = context.get_commands_info(); if let Some(f) = find { let search_string = f.item; let mut found_cmds_vec = Vec::new(); for cmd in full_commands { let mut cols = vec![]; let mut vals = vec![]; let key = cmd.name.clone(); let c = cmd.usage.clone(); let e = cmd.extra_usage.clone(); if key.to_lowercase().contains(&search_string) || c.to_lowercase().contains(&search_string) || e.to_lowercase().contains(&search_string) { cols.push("name".into()); vals.push(Value::String { val: key, span }); cols.push("usage".into()); vals.push(Value::String { val: c, span }); cols.push("extra_usage".into()); vals.push(Value::String { val: e, span }); found_cmds_vec.push(Value::Record { cols, vals, span }); } } return Ok(Value::List { vals: found_cmds_vec, span, }); } if !rest.is_empty() { let mut found_cmds_vec = Vec::new(); if rest[0].item == "commands" { for cmd in full_commands { let mut cols = vec![]; let mut vals = vec![]; let key = cmd.name.clone(); let c = cmd.usage.clone(); let e = cmd.extra_usage.clone(); cols.push("name".into()); vals.push(Value::String { val: key, span }); cols.push("usage".into()); vals.push(Value::String { val: c, span }); cols.push("extra_usage".into()); vals.push(Value::String { val: e, span }); found_cmds_vec.push(Value::Record { cols, vals, span }); } } else { let mut name = String::new(); for r in rest { if !name.is_empty() { name.push(' '); } name.push_str(&r.item); } for cmd in full_commands { let mut cols = vec![]; let mut vals = vec![]; let key = cmd.name.clone(); let c = cmd.usage.clone(); let e = cmd.extra_usage.clone(); if key.starts_with(&name) { cols.push("name".into()); vals.push(Value::String { val: key, span }); cols.push("usage".into()); vals.push(Value::String { val: c, span }); cols.push("extra_usage".into()); vals.push(Value::String { val: e, span }); found_cmds_vec.push(Value::Record { cols, vals, span }); } } } Ok(Value::List { vals: found_cmds_vec, span, }) // FIXME: the fancy help stuff needs to be reimplemented /* if rest[0].item == "commands" { let mut sorted_names = scope.get_command_names(); sorted_names.sort(); let (mut subcommand_names, command_names) = sorted_names .into_iter() // private only commands shouldn't be displayed .filter(|cmd_name| { scope .get_command(cmd_name) .filter(|command| !command.is_private()) .is_some() }) .partition::, _>(|cmd_name| cmd_name.contains(' ')); fn process_name( dict: &mut TaggedDictBuilder, cmd_name: &str, scope: Scope, rest: Vec>, name: Tag, ) -> Result<(), ShellError> { let document_tag = rest[0].tag.clone(); let value = command_dict( scope.get_command(cmd_name).ok_or_else(|| { ShellError::labeled_error( format!("Could not load {}", cmd_name), "could not load command", document_tag, ) })?, name, ); dict.insert_untagged("name", cmd_name); dict.insert_untagged( "description", value .get_data_by_key("usage".spanned_unknown()) .ok_or_else(|| { ShellError::labeled_error( "Expected a usage key", "expected a 'usage' key", &value.tag, ) })? .as_string()?, ); Ok(()) } fn make_subcommands_table( subcommand_names: &mut Vec, cmd_name: &str, scope: Scope, rest: Vec>, name: Tag, ) -> Result { let (matching, not_matching) = subcommand_names.drain(..).partition(|subcommand_name| { subcommand_name.starts_with(&format!("{} ", cmd_name)) }); *subcommand_names = not_matching; Ok(if !matching.is_empty() { UntaggedValue::table( &(matching .into_iter() .map(|cmd_name: String| -> Result<_, ShellError> { let mut short_desc = TaggedDictBuilder::new(name.clone()); process_name( &mut short_desc, &cmd_name, scope.clone(), rest.clone(), name.clone(), )?; Ok(short_desc.into_value()) }) .collect::, _>>()?[..]), ) .into_value(name) } else { UntaggedValue::nothing().into_value(name) }) } let iterator = command_names .into_iter() .map(move |cmd_name| -> Result<_, ShellError> { let mut short_desc = TaggedDictBuilder::new(name.clone()); process_name( &mut short_desc, &cmd_name, scope.clone(), rest.clone(), name.clone(), )?; short_desc.insert_value( "subcommands", make_subcommands_table( &mut subcommand_names, &cmd_name, scope.clone(), rest.clone(), name.clone(), )?, ); ReturnSuccess::value(short_desc.into_value()) }); Ok(iterator.into_action_stream()) } else if rest[0].item == "generate_docs" { Ok(ActionStream::one(ReturnSuccess::value(generate_docs( &scope, )))) } else if rest.len() == 2 { // Check for a subcommand let command_name = format!("{} {}", rest[0].item, rest[1].item); if let Some(command) = scope.get_command(&command_name) { Ok(ActionStream::one(ReturnSuccess::value( UntaggedValue::string(get_full_help(command.stream_command(), &scope)) .into_value(Tag::unknown()), ))) } else { Ok(ActionStream::empty()) } } else if let Some(command) = scope.get_command(&rest[0].item) { Ok(ActionStream::one(ReturnSuccess::value( UntaggedValue::string(get_full_help(command.stream_command(), &scope)) .into_value(Tag::unknown()), ))) } else { Err(ShellError::labeled_error( "Can't find command (use 'help commands' for full list)", "can't find command", rest[0].tag.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 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, }) } } /* fn for_spec(name: &str, ty: &str, required: bool, tag: impl Into) -> Value { let tag = tag.into(); let mut spec = TaggedDictBuilder::new(tag); spec.insert_untagged("name", UntaggedValue::string(name)); spec.insert_untagged("type", UntaggedValue::string(ty)); spec.insert_untagged( "required", UntaggedValue::string(if required { "yes" } else { "no" }), ); spec.into_value() } pub fn signature_dict(signature: Signature, tag: impl Into) -> Value { let tag = tag.into(); let mut sig = TaggedListBuilder::new(&tag); for arg in &signature.positional { let is_required = matches!(arg.0, PositionalType::Mandatory(_, _)); sig.push_value(for_spec(arg.0.name(), "argument", is_required, &tag)); } if signature.rest_positional.is_some() { let is_required = false; sig.push_value(for_spec("rest", "argument", is_required, &tag)); } for (name, ty) in &signature.named { match ty.0 { NamedType::Mandatory(_, _) => sig.push_value(for_spec(name, "flag", true, &tag)), NamedType::Optional(_, _) => sig.push_value(for_spec(name, "flag", false, &tag)), NamedType::Switch(_) => sig.push_value(for_spec(name, "switch", false, &tag)), } } sig.into_value() } fn command_dict(command: Command, tag: impl Into) -> Value { let tag = tag.into(); let mut cmd_dict = TaggedDictBuilder::new(&tag); cmd_dict.insert_untagged("name", UntaggedValue::string(command.name())); cmd_dict.insert_untagged("type", UntaggedValue::string("Command")); cmd_dict.insert_value("signature", signature_dict(command.signature(), tag)); cmd_dict.insert_untagged("usage", UntaggedValue::string(command.usage())); cmd_dict.into_value() } */