From f1b2ab0b2759382a8350b8cb03c4851966f66563 Mon Sep 17 00:00:00 2001 From: onthebridgetonowhere <71919805+onthebridgetonowhere@users.noreply.github.com> Date: Sun, 14 Nov 2021 20:36:24 +0100 Subject: [PATCH] Port str lpad and str rpad (#334) * Port str lpad and str rpad * Remove useless comment Co-authored-by: Stefan Stanciulescu --- crates/nu-command/src/default_context.rs | 2 + crates/nu-command/src/strings/str_/lpad.rs | 180 +++++++++++++++++++++ crates/nu-command/src/strings/str_/mod.rs | 4 + crates/nu-command/src/strings/str_/rpad.rs | 180 +++++++++++++++++++++ 4 files changed, 366 insertions(+) create mode 100644 crates/nu-command/src/strings/str_/lpad.rs create mode 100644 crates/nu-command/src/strings/str_/rpad.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 3db899646..e3ab2bb98 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -113,6 +113,8 @@ pub fn create_default_context() -> EngineState { StrPascalCase, StrScreamingSnakeCase, StrSnakeCase, + StrLpad, + StrRpad, Sys, Table, To, diff --git a/crates/nu-command/src/strings/str_/lpad.rs b/crates/nu-command/src/strings/str_/lpad.rs new file mode 100644 index 000000000..dedbcafa7 --- /dev/null +++ b/crates/nu-command/src/strings/str_/lpad.rs @@ -0,0 +1,180 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::ast::CellPath; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value}; +use std::sync::Arc; + +struct Arguments { + length: Option, + character: Option, + column_paths: Vec, +} + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "str lpad" + } + + fn signature(&self) -> Signature { + Signature::build("str lpad") + .required_named("length", SyntaxShape::Int, "length to pad to", Some('l')) + .required_named( + "character", + SyntaxShape::String, + "character to pad with", + Some('c'), + ) + .rest( + "rest", + SyntaxShape::CellPath, + "optionally check if string contains pattern by column paths", + ) + } + + fn usage(&self) -> &str { + "pad a string with a character a certain length" + } + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + operate(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Left pad a string with a character a number of places", + example: "'nushell' | str lpad -l 10 -c '*'", + result: Some(Value::String { + val: "***nushell".to_string(), + span: Span::unknown(), + }), + }, + Example { + description: "Left pad a string with a character a number of places", + example: "'123' | str lpad -l 10 -c '0'", + result: Some(Value::String { + val: "0000000123".to_string(), + span: Span::unknown(), + }), + }, + Example { + description: "Use lpad to truncate a string", + example: "'123456789' | str lpad -l 3 -c '0'", + result: Some(Value::String { + val: "123".to_string(), + span: Span::unknown(), + }), + }, + Example { + description: "Use lpad to pad Unicode", + example: "'▉' | str lpad -l 10 -c '▉'", + result: Some(Value::String { + val: "▉▉▉▉▉▉▉▉▉▉".to_string(), + span: Span::unknown(), + }), + }, + ] + } +} + +fn operate( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let options = Arc::new(Arguments { + length: call.get_flag(engine_state, stack, "length")?, + character: call.get_flag(engine_state, stack, "character")?, + column_paths: call.rest(engine_state, stack, 0)?, + }); + + let head = call.head; + input.map( + move |v| { + if options.column_paths.is_empty() { + action(&v, &options, head) + } else { + let mut ret = v; + for path in &options.column_paths { + let opt = options.clone(); + let r = ret.update_cell_path( + &path.members, + Box::new(move |old| action(old, &opt, head)), + ); + if let Err(error) = r { + return Value::Error { error }; + } + } + ret + } + }, + engine_state.ctrlc.clone(), + ) +} + +fn action( + input: &Value, + Arguments { + character, length, .. + }: &Arguments, + head: Span, +) -> Value { + match &input { + Value::String { val, .. } => match length { + Some(x) => { + let s = *x as usize; + if s < val.len() { + Value::String { + val: val.chars().take(s).collect::(), + span: head, + } + } else { + let c = character.as_ref().unwrap(); // we already know this flag needs to exist because the command is type checked before we call the action function + let mut res = c.repeat(s - val.chars().count()); + res += val; + Value::String { + val: res, + span: head, + } + } + } + None => Value::Error { + error: ShellError::UnsupportedInput( + String::from("Length argument is missing"), + Span::unknown(), + ), + }, + }, + other => Value::Error { + error: ShellError::UnsupportedInput( + format!( + "Input's type is {}. This command only works with strings.", + other.get_type() + ), + Span::unknown(), + ), + }, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/strings/str_/mod.rs b/crates/nu-command/src/strings/str_/mod.rs index 18a5ada94..e968f22f0 100644 --- a/crates/nu-command/src/strings/str_/mod.rs +++ b/crates/nu-command/src/strings/str_/mod.rs @@ -7,6 +7,8 @@ mod ends_with; mod find_replace; mod index_of; mod length; +mod lpad; +mod rpad; pub use capitalize::SubCommand as StrCapitalize; pub use case::*; @@ -17,3 +19,5 @@ pub use ends_with::SubCommand as StrEndswith; pub use find_replace::SubCommand as StrFindReplace; pub use index_of::SubCommand as StrIndexOf; pub use length::SubCommand as StrLength; +pub use lpad::SubCommand as StrLpad; +pub use rpad::SubCommand as StrRpad; diff --git a/crates/nu-command/src/strings/str_/rpad.rs b/crates/nu-command/src/strings/str_/rpad.rs new file mode 100644 index 000000000..ebbe17313 --- /dev/null +++ b/crates/nu-command/src/strings/str_/rpad.rs @@ -0,0 +1,180 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::ast::CellPath; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value}; +use std::sync::Arc; + +struct Arguments { + length: Option, + character: Option, + column_paths: Vec, +} + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "str rpad" + } + + fn signature(&self) -> Signature { + Signature::build("str rpad") + .required_named("length", SyntaxShape::Int, "length to pad to", Some('l')) + .required_named( + "character", + SyntaxShape::String, + "character to pad with", + Some('c'), + ) + .rest( + "rest", + SyntaxShape::CellPath, + "optionally check if string contains pattern by column paths", + ) + } + + fn usage(&self) -> &str { + "pad a string with a character a certain length" + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + operate(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Right pad a string with a character a number of places", + example: "'nushell' | str rpad -l 10 -c '*'", + result: Some(Value::String { + val: "nushell***".to_string(), + span: Span::unknown(), + }), + }, + Example { + description: "Right pad a string with a character a number of places", + example: "'123' | str rpad -l 10 -c '0'", + result: Some(Value::String { + val: "1230000000".to_string(), + span: Span::unknown(), + }), + }, + Example { + description: "Use rpad to truncate a string", + example: "'123456789' | str rpad -l 3 -c '0'", + result: Some(Value::String { + val: "123".to_string(), + span: Span::unknown(), + }), + }, + Example { + description: "Use rpad to pad Unicode", + example: "'▉' | str rpad -l 10 -c '▉'", + result: Some(Value::String { + val: "▉▉▉▉▉▉▉▉▉▉".to_string(), + span: Span::unknown(), + }), + }, + ] + } +} + +fn operate( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let options = Arc::new(Arguments { + length: call.get_flag(engine_state, stack, "length")?, + character: call.get_flag(engine_state, stack, "character")?, + column_paths: call.rest(engine_state, stack, 0)?, + }); + + let head = call.head; + input.map( + move |v| { + if options.column_paths.is_empty() { + action(&v, &options, head) + } else { + let mut ret = v; + for path in &options.column_paths { + let opt = options.clone(); + let r = ret.update_cell_path( + &path.members, + Box::new(move |old| action(old, &opt, head)), + ); + if let Err(error) = r { + return Value::Error { error }; + } + } + ret + } + }, + engine_state.ctrlc.clone(), + ) +} + +fn action( + input: &Value, + Arguments { + character, length, .. + }: &Arguments, + head: Span, +) -> Value { + match &input { + Value::String { val, .. } => match length { + Some(x) => { + let s = *x as usize; + if s < val.len() { + Value::String { + val: val.chars().take(s).collect::(), + span: head, + } + } else { + let mut res = val.to_string(); + res += &character.as_ref().unwrap().repeat(s - val.chars().count()); + Value::String { + val: res, + span: head, + } + } + } + None => Value::Error { + error: ShellError::UnsupportedInput( + String::from("Length argument is missing"), + Span::unknown(), + ), + }, + }, + other => Value::Error { + error: ShellError::UnsupportedInput( + format!( + "Input's type is {}. This command only works with strings.", + other.get_type() + ), + Span::unknown(), + ), + }, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +}