diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 928320fc26..84fe44fa19 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -260,11 +260,28 @@ fn get_flags_section(signature: &Signature) -> String { if let Some(short) = flag.short { if flag.required { format!( - " -{}, --{} (required parameter){:?} {}\n", - short, flag.long, arg, flag.desc + " -{}{} (required parameter){:?} {}\n", + short, + if !flag.long.is_empty() { + format!(", --{}", flag.long) + } else { + "".into() + }, + arg, + flag.desc ) } else { - format!(" -{}, --{} {:?} {}\n", short, flag.long, arg, flag.desc) + format!( + " -{}{} {:?} {}\n", + short, + if !flag.long.is_empty() { + format!(", --{}", flag.long) + } else { + "".into() + }, + arg, + flag.desc + ) } } else if flag.required { format!( @@ -277,11 +294,26 @@ fn get_flags_section(signature: &Signature) -> String { } else if let Some(short) = flag.short { if flag.required { format!( - " -{}, --{} (required parameter) {}\n", - short, flag.long, flag.desc + " -{}{} (required parameter) {}\n", + short, + if !flag.long.is_empty() { + format!(", --{}", flag.long) + } else { + "".into() + }, + flag.desc ) } else { - format!(" -{}, --{} {}\n", short, flag.long, flag.desc) + format!( + " -{}{} {}\n", + short, + if !flag.long.is_empty() { + format!(", --{}", flag.long) + } else { + "".into() + }, + flag.desc + ) } } else if flag.required { format!(" --{} (required parameter) {}\n", flag.long, flag.desc) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index cfcc2ba85a..0a0367753b 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -2,6 +2,8 @@ use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::EvaluationContext; use nu_protocol::{Range, ShellError, Span, Spanned, Type, Unit, Value}; +use crate::get_full_help; + pub fn eval_operator(op: &Expression) -> Result { match op { Expression { @@ -17,7 +19,13 @@ pub fn eval_operator(op: &Expression) -> Result { fn eval_call(context: &EvaluationContext, call: &Call, input: Value) -> Result { let engine_state = context.engine_state.borrow(); let decl = engine_state.get_decl(call.decl_id); - if let Some(block_id) = decl.get_block_id() { + if call.named.iter().any(|(flag, _)| flag.item == "help") { + let full_help = get_full_help(&decl.signature(), &decl.examples(), context); + Ok(Value::String { + val: full_help, + span: call.head, + }) + } else if let Some(block_id) = decl.get_block_id() { let state = context.enter_scope(); for (arg, param) in call.positional.iter().zip( decl.signature() @@ -62,38 +70,36 @@ fn eval_call(context: &EvaluationContext, call: &Call, input: Value) -> Result flatten_block(working_set, working_set.get_block(*block_id)), Expr::Call(call) => { let mut output = vec![(call.head, FlatShape::InternalCall)]; + + let mut args = vec![]; for positional in &call.positional { - output.extend(flatten_expression(working_set, positional)); + args.extend(flatten_expression(working_set, positional)); } for named in &call.named { - output.push((named.0.span, FlatShape::Flag)); + args.push((named.0.span, FlatShape::Flag)); if let Some(expr) = &named.1 { - output.extend(flatten_expression(working_set, expr)); + args.extend(flatten_expression(working_set, expr)); } } + // sort these since flags and positional args can be intermixed + args.sort(); + + output.extend(args); output } Expr::ExternalCall(_, name_span, args) => { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 7a56d41e42..8a5a2c1401 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -52,6 +52,11 @@ fn is_variable(bytes: &[u8]) -> bool { } fn check_call(command: Span, sig: &Signature, call: &Call) -> Option { + // Allow the call to pass if they pass in the help flag + if call.named.iter().any(|(n, _)| n.item == "help") { + return None; + } + if call.positional.len() < sig.required_positional.len() { let missing = &sig.required_positional[call.positional.len()]; Some(ParseError::MissingPositional(missing.name.clone(), command)) @@ -153,6 +158,7 @@ fn parse_long_flag( let split: Vec<_> = arg_contents.split(|x| *x == b'=').collect(); let long_name = String::from_utf8(split[0].into()); if let Ok(long_name) = long_name { + let long_name = long_name[2..].to_string(); if let Some(flag) = sig.get_long_flag(&long_name) { if let Some(arg_shape) = &flag.arg { if split.len() > 1 { @@ -1974,7 +1980,7 @@ pub fn parse_signature_helper( let flags: Vec<_> = contents.split(|x| x == &b'(').map(|x| x.to_vec()).collect(); - let long = String::from_utf8_lossy(&flags[0]).to_string(); + let long = String::from_utf8_lossy(&flags[0][2..]).to_string(); let variable_name = flags[0][2..].to_vec(); let var_id = working_set.add_variable(variable_name, Type::Unknown); @@ -2003,7 +2009,7 @@ pub fn parse_signature_helper( let short_flag = String::from_utf8_lossy(short_flag).to_string(); let chars: Vec = short_flag.chars().collect(); - let long = String::from_utf8_lossy(&flags[0]).to_string(); + let long = String::from_utf8_lossy(&flags[0][2..]).to_string(); let variable_name = flags[0][2..].to_vec(); let var_id = working_set.add_variable(variable_name, Type::Unknown); diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 26393f37e0..7a418767f3 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -97,7 +97,7 @@ pub fn parse_call_missing_flag_arg() { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); - let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); + let sig = Signature::build("foo").named("jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl(sig.predeclare()); let (_, err) = parse(&mut working_set, None, b"foo --jazz", true); diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index d1abc77330..e29d4b79c9 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -54,6 +54,16 @@ impl Eq for Signature {} impl Signature { pub fn new(name: impl Into) -> Signature { + // default help flag + let flag = Flag { + long: "help".into(), + short: Some('h'), + arg: None, + desc: "Display this help message".into(), + required: false, + var_id: None, + }; + Signature { name: name.into(), usage: String::new(), @@ -61,7 +71,7 @@ impl Signature { required_positional: vec![], optional_positional: vec![], rest_positional: None, - named: vec![], + named: vec![flag], is_filter: false, creates_scope: false, } diff --git a/crates/nu-protocol/src/span.rs b/crates/nu-protocol/src/span.rs index 744915783a..b4b32cec64 100644 --- a/crates/nu-protocol/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -10,7 +10,7 @@ where pub span: Span, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct Span { pub start: usize, pub end: usize, @@ -37,6 +37,10 @@ impl Span { end: self.end - offset, } } + + pub fn contains(&self, pos: usize) -> bool { + pos >= self.start && pos < self.end + } } pub fn span(spans: &[Span]) -> Span { diff --git a/crates/nu-protocol/tests/test_signature.rs b/crates/nu-protocol/tests/test_signature.rs index a814325809..5559d7e593 100644 --- a/crates/nu-protocol/tests/test_signature.rs +++ b/crates/nu-protocol/tests/test_signature.rs @@ -31,10 +31,13 @@ fn test_signature_chained() { assert_eq!(signature.required_positional.len(), 1); assert_eq!(signature.optional_positional.len(), 1); - assert_eq!(signature.named.len(), 3); + assert_eq!(signature.named.len(), 4); // The 3 above + help assert!(signature.rest_positional.is_some()); - assert_eq!(signature.get_shorts(), vec!['r', 'n']); - assert_eq!(signature.get_names(), vec!["req_named", "named", "switch"]); + assert_eq!(signature.get_shorts(), vec!['h', 'r', 'n']); + assert_eq!( + signature.get_names(), + vec!["help", "req_named", "named", "switch"] + ); assert_eq!(signature.num_positionals(), 2); assert_eq!( diff --git a/src/tests.rs b/src/tests.rs index e0e2d1508d..da428731f1 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -764,3 +764,16 @@ fn custom_switch4() -> TestResult { fn bad_var_name() -> TestResult { fail_test(r#"let $"foo bar" = 4"#, "can't contain") } + +#[test] +fn long_flag() -> TestResult { + run_test( + r#"([a, b, c] | each --numbered { if $it.index == 1 { 100 } else { 0 } }).1"#, + "100", + ) +} + +#[test] +fn help_works_with_missing_requirements() -> TestResult { + run_test(r#"each --help | lines | length"#, "10") +}