diff --git a/crates/nu-parser/src/hir.rs b/crates/nu-parser/src/hir.rs index 6c79d77c..8baa3608 100644 --- a/crates/nu-parser/src/hir.rs +++ b/crates/nu-parser/src/hir.rs @@ -71,6 +71,19 @@ pub struct Call { pub span: Span, } +impl Call { + pub fn switch_preset(&self, switch: &str) -> bool { + self.named + .as_ref() + .and_then(|n| n.get(switch)) + .map(|t| match t { + NamedValue::PresentSwitch(_) => true, + _ => false, + }) + .unwrap_or(false) + } +} + impl PrettyDebugWithSource for Call { fn pretty_debug(&self, source: &str) -> DebugDocBuilder { b::delimit( diff --git a/crates/nu-parser/src/hir/baseline_parse/tests.rs b/crates/nu-parser/src/hir/baseline_parse/tests.rs index e73f1b8e..0ed1dc69 100644 --- a/crates/nu-parser/src/hir/baseline_parse/tests.rs +++ b/crates/nu-parser/src/hir/baseline_parse/tests.rs @@ -72,6 +72,7 @@ fn test_parse_command() { let mut map = IndexMap::new(); map.insert("full".to_string(), NamedValue::AbsentSwitch); + map.insert("help".to_string(), NamedValue::AbsentSwitch); ClassifiedCommand::Internal(InternalCommand::new( "ls".to_string(), diff --git a/crates/nu-parser/src/hir/named.rs b/crates/nu-parser/src/hir/named.rs index cc0290e5..45ca381d 100644 --- a/crates/nu-parser/src/hir/named.rs +++ b/crates/nu-parser/src/hir/named.rs @@ -37,6 +37,10 @@ impl NamedArguments { pub fn iter(&self) -> impl Iterator { self.named.iter() } + + pub fn get(&self, name: &str) -> Option<&NamedValue> { + self.named.get(name) + } } impl NamedArguments { diff --git a/crates/nu-parser/src/parse_command.rs b/crates/nu-parser/src/parse_command.rs index ab504d6c..0f13aea7 100644 --- a/crates/nu-parser/src/parse_command.rs +++ b/crates/nu-parser/src/parse_command.rs @@ -31,6 +31,14 @@ pub fn parse_command_tail( named.insert_switch(name, flag); } + NamedType::Help => { + let flag = extract_switch(name, tail, context.source()); + + named.insert_switch(name, flag); + if flag.is_some() { + return Ok(Some((None, Some(named)))); + } + } NamedType::Mandatory(syntax_type) => { match extract_mandatory(config, name, tail, context.source(), command_span) { Err(err) => return Err(err), // produce a correct diagnostic @@ -242,7 +250,7 @@ impl ColorSyntax for CommandTailShape { trace!(target: "nu::color_syntax", "looking for {} : {:?}", name, kind); match &kind.0 { - NamedType::Switch => { + NamedType::Switch | NamedType::Help => { if let Some((pos, flag)) = token_nodes.extract(|t| t.as_flag(name, context.source())) { diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 3751e73a..117c89be 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -13,6 +13,7 @@ pub enum NamedType { Mandatory(SyntaxShape), /// An optional flag, with associated argument. eg) `foo --optional abc` Optional(SyntaxShape), + Help, } /// The type of positional arguments @@ -135,7 +136,7 @@ impl Signature { usage: String::new(), positional: vec![], rest_positional: None, - named: IndexMap::new(), + named: indexmap::indexmap! {"help".into() => (NamedType::Help, "Display this help message".into())}, is_filter: false, yields: None, input: None, @@ -217,6 +218,13 @@ impl Signature { self } + /// Remove the default help switch + pub fn remove_help(mut self) -> Signature { + self.named.remove("help"); + + self + } + /// Set the filter flag for the signature pub fn filter(mut self) -> Signature { self.is_filter = true; diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 9dcaa1d2..ff586b48 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -243,6 +243,13 @@ impl Value { _ => Err(ShellError::type_error("integer", self.spanned_type_name())), } } + + pub fn as_bool(&self) -> Result { + match &self.value { + UntaggedValue::Primitive(Primitive::Boolean(p)) => Ok(*p), + _ => Err(ShellError::type_error("boolean", self.spanned_type_name())), + } + } } impl Into for &str { diff --git a/src/commands/command.rs b/src/commands/command.rs index 89d6db2e..f04bc8f1 100644 --- a/src/commands/command.rs +++ b/src/commands/command.rs @@ -1,3 +1,4 @@ +use crate::commands::help::get_help; use crate::context::CommandRegistry; use crate::deserializer::ConfigDeserializer; use crate::evaluate::evaluate_args::evaluate_args; @@ -31,6 +32,10 @@ impl UnevaluatedCallInfo { name_tag: self.name_tag, }) } + + pub fn switch_present(&self, switch: &str) -> bool { + self.args.switch_preset(switch) + } } pub trait CallInfoExt { @@ -476,12 +481,18 @@ impl Command { } pub fn run(&self, args: CommandArgs, registry: &CommandRegistry) -> OutputStream { - match self { - Command::WholeStream(command) => match command.run(args, registry) { - Ok(stream) => stream, - Err(err) => OutputStream::one(Err(err)), - }, - Command::PerItem(command) => self.run_helper(command.clone(), args, registry.clone()), + if args.call_info.switch_present("help") { + get_help(self.name(), self.usage(), self.signature()).into() + } else { + match self { + Command::WholeStream(command) => match command.run(args, registry) { + Ok(stream) => stream, + Err(err) => OutputStream::one(Err(err)), + }, + Command::PerItem(command) => { + self.run_helper(command.clone(), args, registry.clone()) + } + } } } diff --git a/src/commands/help.rs b/src/commands/help.rs index 17b63efc..6897ac79 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -1,5 +1,6 @@ use crate::commands::PerItemCommand; use crate::data::command_dict; + use crate::prelude::*; use nu_errors::ShellError; use nu_protocol::{ @@ -72,94 +73,10 @@ impl PerItemCommand for Help { help.push_back(ReturnSuccess::value(short_desc.into_value())); } } else if let Some(command) = registry.get_command(document)? { - let mut long_desc = String::new(); - - long_desc.push_str(&command.usage()); - long_desc.push_str("\n"); - - let signature = command.signature(); - - let mut one_liner = String::new(); - one_liner.push_str(&signature.name); - one_liner.push_str(" "); - - for positional in &signature.positional { - match &positional.0 { - PositionalType::Mandatory(name, _m) => { - one_liner.push_str(&format!("<{}> ", name)); - } - PositionalType::Optional(name, _o) => { - one_liner.push_str(&format!("({}) ", name)); - } - } - } - - if signature.rest_positional.is_some() { - one_liner.push_str(" ...args"); - } - - if !signature.named.is_empty() { - one_liner.push_str("{flags} "); - } - - long_desc.push_str(&format!("\nUsage:\n > {}\n", one_liner)); - - if !signature.positional.is_empty() || signature.rest_positional.is_some() { - long_desc.push_str("\nparameters:\n"); - for positional in signature.positional { - match positional.0 { - PositionalType::Mandatory(name, _m) => { - long_desc.push_str(&format!(" <{}> {}\n", name, positional.1)); - } - PositionalType::Optional(name, _o) => { - long_desc.push_str(&format!(" ({}) {}\n", name, positional.1)); - } - } - } - - if let Some(rest_positional) = signature.rest_positional { - long_desc.push_str(&format!(" ...args: {}\n", rest_positional.1)); - } - } - if !signature.named.is_empty() { - long_desc.push_str("\nflags:\n"); - for (flag, ty) in signature.named { - match ty.0 { - NamedType::Switch => { - long_desc.push_str(&format!( - " --{}{} {}\n", - flag, - if !ty.1.is_empty() { ":" } else { "" }, - ty.1 - )); - } - NamedType::Mandatory(m) => { - long_desc.push_str(&format!( - " --{} <{}> (required parameter){} {}\n", - flag, - m.display(), - if !ty.1.is_empty() { ":" } else { "" }, - ty.1 - )); - } - NamedType::Optional(o) => { - long_desc.push_str(&format!( - " --{} <{}>{} {}\n", - flag, - o.display(), - if !ty.1.is_empty() { ":" } else { "" }, - ty.1 - )); - } - } - } - } - - help.push_back(ReturnSuccess::value( - UntaggedValue::string(long_desc).into_value(tag.clone()), - )); + return Ok( + get_help(&command.name(), &command.usage(), command.signature()).into(), + ); } - Ok(help.to_output_stream()) } _ => { @@ -182,3 +99,98 @@ You can also learn more at https://www.nushell.sh/book/"#; } } } + +pub(crate) fn get_help( + cmd_name: &str, + cmd_usage: &str, + cmd_sig: Signature, +) -> impl Into { + let mut help = VecDeque::new(); + let mut long_desc = String::new(); + + long_desc.push_str(&cmd_usage); + long_desc.push_str("\n"); + + let signature = cmd_sig; + + let mut one_liner = String::new(); + one_liner.push_str(&signature.name); + one_liner.push_str(" "); + + for positional in &signature.positional { + match &positional.0 { + PositionalType::Mandatory(name, _m) => { + one_liner.push_str(&format!("<{}> ", name)); + } + PositionalType::Optional(name, _o) => { + one_liner.push_str(&format!("({}) ", name)); + } + } + } + + if signature.rest_positional.is_some() { + one_liner.push_str(" ...args"); + } + + if !signature.named.is_empty() { + one_liner.push_str("{flags} "); + } + + long_desc.push_str(&format!("\nUsage:\n > {}\n", one_liner)); + + if !signature.positional.is_empty() || signature.rest_positional.is_some() { + long_desc.push_str("\nparameters:\n"); + for positional in signature.positional { + match positional.0 { + PositionalType::Mandatory(name, _m) => { + long_desc.push_str(&format!(" <{}> {}\n", name, positional.1)); + } + PositionalType::Optional(name, _o) => { + long_desc.push_str(&format!(" ({}) {}\n", name, positional.1)); + } + } + } + + if let Some(rest_positional) = signature.rest_positional { + long_desc.push_str(&format!(" ...args: {}\n", rest_positional.1)); + } + } + if !signature.named.is_empty() { + long_desc.push_str("\nflags:\n"); + for (flag, ty) in signature.named { + match ty.0 { + NamedType::Switch | NamedType::Help => { + long_desc.push_str(&format!( + " --{}{} {}\n", + flag, + if !ty.1.is_empty() { ":" } else { "" }, + ty.1 + )); + } + NamedType::Mandatory(m) => { + long_desc.push_str(&format!( + " --{} <{}> (required parameter){} {}\n", + flag, + m.display(), + if !ty.1.is_empty() { ":" } else { "" }, + ty.1 + )); + } + NamedType::Optional(o) => { + long_desc.push_str(&format!( + " --{} <{}>{} {}\n", + flag, + o.display(), + if !ty.1.is_empty() { ":" } else { "" }, + ty.1 + )); + } + } + } + } + + help.push_back(ReturnSuccess::value( + UntaggedValue::string(long_desc).into_value(Tag::from((0, cmd_name.len(), None))), + )); + help +} diff --git a/src/data/command.rs b/src/data/command.rs index 91c52bc0..bec6074c 100644 --- a/src/data/command.rs +++ b/src/data/command.rs @@ -63,6 +63,7 @@ fn signature_dict(signature: Signature, tag: impl Into) -> Value { 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)), + NamedType::Help => sig.push_value(for_spec("help", "switch", false, &tag)), } }