diff --git a/crates/nu-command/src/generators/seq_char.rs b/crates/nu-command/src/generators/seq_char.rs index 528615603f..33ce9144ac 100644 --- a/crates/nu-command/src/generators/seq_char.rs +++ b/crates/nu-command/src/generators/seq_char.rs @@ -14,27 +14,21 @@ impl Command for SeqChar { } fn usage(&self) -> &str { - "Print sequence of chars" + "Print a sequence of ASCII characters" } fn signature(&self) -> Signature { Signature::build("seq char") - .input_output_types(vec![ - (Type::Nothing, Type::List(Box::new(Type::Any))), - (Type::Nothing, Type::String), - ]) - .rest("rest", SyntaxShape::String, "sequence chars") - .named( - "separator", + .input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::String)))]) + .required( + "start", SyntaxShape::String, - "separator character (defaults to \\n)", - Some('s'), + "start of character sequence (inclusive)", ) - .named( - "terminator", + .required( + "end", SyntaxShape::String, - "terminator character (defaults to \\n)", - Some('t'), + "end of character sequence (inclusive)", ) .category(Category::Generators) } @@ -42,7 +36,7 @@ impl Command for SeqChar { fn examples(&self) -> Vec { vec![ Example { - description: "sequence a to e with newline separator", + description: "sequence a to e", example: "seq char a e", result: Some(Value::List { vals: vec![ @@ -56,9 +50,10 @@ impl Command for SeqChar { }), }, Example { - description: "sequence a to e with pipe separator separator", - example: "seq char -s '|' a e", - result: Some(Value::test_string("a|b|c|d|e")), + description: "sequence a to e, and put the characters in a pipe-separated string", + example: "seq char a e | str join '|'", + // TODO: it would be nice to test this example, but it currently breaks the input/output type tests + result: None, }, ] } @@ -83,118 +78,58 @@ fn seq_char( stack: &mut Stack, call: &Call, ) -> Result { - // input check. - let separator: Option> = call.get_flag(engine_state, stack, "separator")?; - let terminator: Option> = call.get_flag(engine_state, stack, "terminator")?; - let rest_inputs: Vec> = call.rest(engine_state, stack, 0)?; + let start: Spanned = call.req(engine_state, stack, 0)?; + let end: Spanned = call.req(engine_state, stack, 1)?; - let (start_ch, end_ch) = if rest_inputs.len() != 2 - || !is_single_character(&rest_inputs[0].item) - || !is_single_character(&rest_inputs[1].item) - { + if !is_single_character(&start.item) { return Err(ShellError::GenericError( - "seq char required two character parameters".into(), - "needs parameter".into(), - Some(call.head), + "seq char only accepts individual ASCII characters as parameters".into(), + "should be 1 character long".into(), + Some(start.span), None, Vec::new(), )); - } else { - // unwrap here is ok, because we just check the length of `rest_inputs`. - ( - rest_inputs[0] - .item - .chars() - .next() - .expect("seq char input must contains 2 inputs"), - rest_inputs[1] - .item - .chars() - .next() - .expect("seq char input must contains 2 inputs"), - ) - }; + } - let sep: String = match separator { - Some(s) => { - if s.item == r"\t" { - '\t'.to_string() - } else if s.item == r"\n" { - '\n'.to_string() - } else if s.item == r"\r" { - '\r'.to_string() - } else { - let vec_s: Vec = s.item.chars().collect(); - if vec_s.is_empty() { - return Err(ShellError::GenericError( - "Expected a single separator char from --separator".into(), - "requires a single character string input".into(), - Some(s.span), - None, - Vec::new(), - )); - }; - vec_s.iter().collect() - } - } - _ => '\n'.to_string(), - }; + if !is_single_character(&end.item) { + return Err(ShellError::GenericError( + "seq char only accepts individual ASCII characters as parameters".into(), + "should be 1 character long".into(), + Some(end.span), + None, + Vec::new(), + )); + } - let terminator: String = match terminator { - Some(t) => { - if t.item == r"\t" { - '\t'.to_string() - } else if t.item == r"\n" { - '\n'.to_string() - } else if t.item == r"\r" { - '\r'.to_string() - } else { - let vec_t: Vec = t.item.chars().collect(); - if vec_t.is_empty() { - return Err(ShellError::GenericError( - "Expected a single terminator char from --terminator".into(), - "requires a single character string input".into(), - Some(t.span), - None, - Vec::new(), - )); - }; - vec_t.iter().collect() - } - } - _ => '\n'.to_string(), - }; + let start = start + .item + .chars() + .next() + // expect is ok here, because we just checked the length + .expect("seq char input must contains 2 inputs"); + + let end = end + .item + .chars() + .next() + // expect is ok here, because we just checked the length + .expect("seq char input must contains 2 inputs"); let span = call.head; - run_seq_char(start_ch, end_ch, sep, terminator, span) + run_seq_char(start, end, span) } -fn run_seq_char( - start_ch: char, - end_ch: char, - sep: String, - terminator: String, - span: Span, -) -> Result { +fn run_seq_char(start_ch: char, end_ch: char, span: Span) -> Result { let mut result_vec = vec![]; for current_ch in start_ch as u8..end_ch as u8 + 1 { result_vec.push((current_ch as char).to_string()) } - let return_list = (sep == "\n" || sep == "\r") && (terminator == "\n" || terminator == "\r"); - if return_list { - let result = result_vec - .into_iter() - .map(|x| Value::String { val: x, span }) - .collect::>(); - Ok(Value::List { vals: result, span }.into_pipeline_data()) - } else { - let mut result = result_vec.join(&sep); - result.push_str(&terminator); - // doesn't output a list, if separator is '\n', it's better to eliminate them. - // and it matches `seq` behavior. - let result = result.lines().collect(); - Ok(Value::String { val: result, span }.into_pipeline_data()) - } + + let result = result_vec + .into_iter() + .map(|x| Value::String { val: x, span }) + .collect::>(); + Ok(Value::List { vals: result, span }.into_pipeline_data()) } #[cfg(test)] diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index dc06030b78..103c53ab1d 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -67,6 +67,7 @@ mod run_external; mod save; mod select; mod semicolon; +mod seq_char; mod shells; mod skip; mod sort_by; diff --git a/crates/nu-command/tests/commands/seq_char.rs b/crates/nu-command/tests/commands/seq_char.rs new file mode 100644 index 0000000000..144914d041 --- /dev/null +++ b/crates/nu-command/tests/commands/seq_char.rs @@ -0,0 +1,25 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn fails_when_first_arg_is_multiple_chars() { + let actual = nu!( + cwd: ".", pipeline( + r#" + seq char aa z + "# + )); + + assert!(actual.err.contains("should be 1 character long")); +} + +#[test] +fn fails_when_second_arg_is_multiple_chars() { + let actual = nu!( + cwd: ".", pipeline( + r#" + seq char a zz + "# + )); + + assert!(actual.err.contains("should be 1 character long")); +}