From 4accc6784377c4bc70e22af7e4f0c7a24f6b3622 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 8 Aug 2023 08:33:01 +1200 Subject: [PATCH] Move `help commands` to use more structure in signatures (#9949) # Description Signatures in `help commands` will now have more structure for params and input/output pairs. Example: Improved params ![image](https://github.com/nushell/nushell/assets/547158/f5dacaf2-861b-4b44-aaa6-e17b4bcb953e) Improved input/output pairs ![image](https://github.com/nushell/nushell/assets/547158/844a6e9c-dbfc-4c07-b0ef-fefd835a4cf0) # User-Facing Changes This is technically a breaking change if previous code assumed the shape of things in `help commands`. # Tests + Formatting # After Submitting --- crates/nu-command/src/help/help_commands.rs | 135 ++++++++++++++++++-- crates/nu-protocol/src/plugin_signature.rs | 6 - crates/nu-protocol/src/signature.rs | 66 ---------- 3 files changed, 125 insertions(+), 82 deletions(-) diff --git a/crates/nu-command/src/help/help_commands.rs b/crates/nu-command/src/help/help_commands.rs index 9fc01f84cb..ce360a244e 100644 --- a/crates/nu-command/src/help/help_commands.rs +++ b/crates/nu-command/src/help/help_commands.rs @@ -135,7 +135,6 @@ fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec { let decl = engine_state.get_decl(decl_id); let sig = decl.signature().update_from_command(name, decl.borrow()); - let signatures = sig.to_string().trim_start().replace("\n ", "\n"); let key = sig.name; let usage = sig.usage; let search_terms = sig.search_terms; @@ -155,15 +154,131 @@ fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec { cols.push("usage".into()); vals.push(Value::String { val: usage, span }); - cols.push("signatures".into()); - vals.push(Value::String { - val: if decl.is_parser_keyword() { - "".to_string() - } else { - signatures - }, - span, - }); + cols.push("params".into()); + + // Build table of parameters + let param_table = { + let mut vals = vec![]; + + for required_param in &sig.required_positional { + vals.push(Value::Record { + cols: vec![ + "name".to_string(), + "type".to_string(), + "required".to_string(), + "description".to_string(), + ], + vals: vec![ + Value::string(&required_param.name, span), + Value::string(required_param.shape.to_string(), span), + Value::bool(true, span), + Value::string(&required_param.desc, span), + ], + span, + }); + } + + for optional_param in &sig.optional_positional { + vals.push(Value::Record { + cols: vec![ + "name".to_string(), + "type".to_string(), + "required".to_string(), + "description".to_string(), + ], + vals: vec![ + Value::string(&optional_param.name, span), + Value::string(optional_param.shape.to_string(), span), + Value::bool(false, span), + Value::string(&optional_param.desc, span), + ], + span, + }); + } + + if let Some(rest_positional) = &sig.rest_positional { + vals.push(Value::Record { + cols: vec![ + "name".to_string(), + "type".to_string(), + "required".to_string(), + "description".to_string(), + ], + vals: vec![ + Value::string(format!("...{}", rest_positional.name), span), + Value::string(rest_positional.shape.to_string(), span), + Value::bool(false, span), + Value::string(&rest_positional.desc, span), + ], + span, + }); + } + + for named_param in &sig.named { + let name = if let Some(short) = named_param.short { + if named_param.long.is_empty() { + format!("-{}", short) + } else { + format!("--{}(-{})", named_param.long, short) + } + } else { + format!("--{}", named_param.long) + }; + + vals.push(Value::Record { + cols: vec![ + "name".to_string(), + "type".to_string(), + "required".to_string(), + "description".to_string(), + ], + vals: vec![ + Value::string(name, span), + Value::string( + if let Some(arg) = &named_param.arg { + arg.to_string() + } else { + "switch".to_string() + }, + span, + ), + Value::bool(named_param.required, span), + Value::string(&named_param.desc, span), + ], + span, + }); + } + + Value::List { vals, span } + }; + vals.push(param_table); + + cols.push("input_output".into()); + + // Build the signature input/output table + let input_output_table = { + let mut vals = vec![]; + + for (input_type, output_type) in sig.input_output_types { + vals.push(Value::Record { + cols: vec!["input".to_string(), "output".to_string()], + vals: vec![ + Value::String { + val: input_type.to_string(), + span, + }, + Value::String { + val: output_type.to_string(), + span, + }, + ], + span, + }); + } + + Value::List { vals, span } + }; + vals.push(input_output_table); cols.push("search_terms".into()); vals.push(Value::String { diff --git a/crates/nu-protocol/src/plugin_signature.rs b/crates/nu-protocol/src/plugin_signature.rs index b4ac8deb48..215a7e2ab2 100644 --- a/crates/nu-protocol/src/plugin_signature.rs +++ b/crates/nu-protocol/src/plugin_signature.rs @@ -12,12 +12,6 @@ pub struct PluginSignature { pub examples: Vec, } -impl std::fmt::Display for PluginSignature { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.sig) - } -} - impl PluginSignature { pub fn new(sig: Signature, examples: Vec) -> Self { Self { sig, examples } diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 8b453d7d71..81168b32b5 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -124,72 +124,6 @@ pub struct Signature { pub category: Category, } -/// Format argument type for user readable output. -/// -/// In general: -/// if argument type is a simple type(like string), we'll wrapped with `<>`, the result will be `` -/// if argument type is already contains `<>`, like `list`, the result will be `list`. -fn fmt_type(arg_type: &Type, optional: bool) -> String { - let arg_type = arg_type.to_string(); - if arg_type.contains('<') && arg_type.contains('>') { - if optional { - format!("{arg_type}?") - } else { - arg_type - } - } else if optional { - format!("<{arg_type}?>") - } else { - format!("<{arg_type}>") - } -} - -// in general, a commands signature should looks like this: -// -// | , => string -// -// More detail explanation: -// the first one is the input from previous command, aka, pipeline input -// then followed by `|`, then positional arguments type -// then optional arguments type, which ends with `?` -// Then followed by `->` -// Finally output type. -// -// If a command contains multiple input/output types, separate them in different lines. -impl std::fmt::Display for Signature { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut args = self - .required_positional - .iter() - .map(|p| fmt_type(&p.shape.to_type(), false)) - .collect::>(); - args.append( - &mut self - .optional_positional - .iter() - .map(|p| fmt_type(&p.shape.to_type(), true)) - .collect::>(), - ); - let args = args.join(", "); - - let mut signatures = vec![]; - for (input_type, output_type) in self.input_output_types.iter() { - // ident with two spaces for user friendly output. - let input_type = fmt_type(input_type, false); - let output_type = fmt_type(output_type, false); - if args.is_empty() { - signatures.push(format!(" {input_type} | {} -> {output_type}", self.name)) - } else { - signatures.push(format!( - " {input_type} | {} {args} -> {output_type}", - self.name - )) - } - } - write!(f, "{}", signatures.join("\n")) - } -} - impl PartialEq for Signature { fn eq(&self, other: &Self) -> bool { self.name == other.name