From c2ea993f7eabfa62df468cc72d39cbd4dcc84e3c Mon Sep 17 00:00:00 2001 From: WindSoilder Date: Fri, 6 May 2022 23:40:02 +0800 Subject: [PATCH] implement seq char command to generate single character sequence (#5453) * add tmp code * add seq char command --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/generators/mod.rs | 2 + crates/nu-command/src/generators/seq_char.rs | 206 +++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 crates/nu-command/src/generators/seq_char.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index bebb4ceca..c28bd98f2 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -353,6 +353,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Cal, Seq, SeqDate, + SeqChar, }; // Hash diff --git a/crates/nu-command/src/generators/mod.rs b/crates/nu-command/src/generators/mod.rs index c150009e5..0694a9d25 100644 --- a/crates/nu-command/src/generators/mod.rs +++ b/crates/nu-command/src/generators/mod.rs @@ -1,7 +1,9 @@ mod cal; mod seq; +mod seq_char; mod seq_date; pub use cal::Cal; pub use seq::Seq; +pub use seq_char::SeqChar; pub use seq_date::SeqDate; diff --git a/crates/nu-command/src/generators/seq_char.rs b/crates/nu-command/src/generators/seq_char.rs new file mode 100644 index 000000000..15f11a75b --- /dev/null +++ b/crates/nu-command/src/generators/seq_char.rs @@ -0,0 +1,206 @@ +use nu_engine::CallExt; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + ast::Call, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, + Spanned, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct SeqChar; + +impl Command for SeqChar { + fn name(&self) -> &str { + "seq char" + } + + fn usage(&self) -> &str { + "Print sequence of chars" + } + + fn signature(&self) -> Signature { + Signature::build("seq char") + .rest("rest", SyntaxShape::String, "sequence chars") + .named( + "separator", + SyntaxShape::String, + "separator character (defaults to \\n)", + Some('s'), + ) + .named( + "terminator", + SyntaxShape::String, + "terminator character (defaults to \\n)", + Some('t'), + ) + .category(Category::Generators) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "sequence a to e with newline separator", + example: "seq char a e", + result: Some(Value::List { + vals: vec![ + Value::test_string('a'), + Value::test_string('b'), + Value::test_string('c'), + Value::test_string('d'), + Value::test_string('e'), + ], + span: Span::test_data(), + }), + }, + 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")), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + seq_char(engine_state, stack, call) + } +} + +fn is_single_character(ch: &str) -> bool { + ch.is_ascii() && ch.len() == 1 && ch.chars().all(char::is_alphabetic) +} + +fn seq_char( + engine_state: &EngineState, + 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_ch, end_ch) = if rest_inputs.len() != 2 + || !is_single_character(&rest_inputs[0].item) + || !is_single_character(&rest_inputs[1].item) + { + return Err(ShellError::GenericError( + "seq char required two character parameters".into(), + "needs parameter".into(), + Some(call.head), + 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(), + }; + + 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 span = call.head; + run_seq_char(start_ch, end_ch, sep, terminator, span) +} + +fn run_seq_char( + start_ch: char, + end_ch: char, + sep: String, + terminator: String, + 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()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SeqChar {}) + } +}