diff --git a/crates/nu-cli/src/commands/str_/from.rs b/crates/nu-cli/src/commands/str_/from.rs index 4344723b4e..3fe74e2766 100644 --- a/crates/nu-cli/src/commands/str_/from.rs +++ b/crates/nu-cli/src/commands/str_/from.rs @@ -207,7 +207,7 @@ fn format_decimal(mut decimal: BigDecimal, digits: Option, group_digits: bo .take(n as usize) .collect() } else { - trim_char(dec_part, '0', false, true) + trim_char(&dec_part, '0', false, true) }; let format_default_loc = |int_part: BigInt| { diff --git a/crates/nu-cli/src/commands/str_/trim.rs b/crates/nu-cli/src/commands/str_/trim.rs index d96f4daa8b..9eac230a3d 100644 --- a/crates/nu-cli/src/commands/str_/trim.rs +++ b/crates/nu-cli/src/commands/str_/trim.rs @@ -5,12 +5,14 @@ use nu_protocol::ShellTypeName; use nu_protocol::{ ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value, }; -use nu_source::Tag; +use nu_source::{Tag, Tagged}; use nu_value_ext::ValueExt; #[derive(Deserialize)] struct Arguments { rest: Vec, + #[serde(rename(deserialize = "char"))] + char_: Option>, } pub struct SubCommand; @@ -22,10 +24,17 @@ impl WholeStreamCommand for SubCommand { } fn signature(&self) -> Signature { - Signature::build("str trim").rest( - SyntaxShape::ColumnPath, - "optionally trim text by column paths", - ) + Signature::build("str trim") + .rest( + SyntaxShape::ColumnPath, + "optionally trim text by column paths", + ) + .named( + "char", + SyntaxShape::String, + "character to trim (default: whitespace)", + Some('c'), + ) } fn usage(&self) -> &str { @@ -41,11 +50,18 @@ impl WholeStreamCommand for SubCommand { } fn examples(&self) -> Vec { - vec![Example { - description: "Trim contents", - example: "echo 'Nu shell ' | str trim", - result: Some(vec![Value::from("Nu shell")]), - }] + vec![ + Example { + description: "Trim whitespace", + example: "echo 'Nu shell ' | str trim", + result: Some(vec![Value::from("Nu shell")]), + }, + Example { + description: "Trim a specific character", + example: "echo '=== Nu shell ===' | str trim -c '=' | str trim", + result: Some(vec![Value::from("Nu shell")]), + }, + ] } } @@ -55,14 +71,15 @@ async fn operate( ) -> Result { let registry = registry.clone(); - let (Arguments { rest }, input) = args.process(®istry).await?; + let (Arguments { rest, char_ }, input) = args.process(®istry).await?; let column_paths: Vec<_> = rest; + let to_trim = char_.map(|tagged| tagged.item); Ok(input .map(move |v| { if column_paths.is_empty() { - match action(&v, v.tag()) { + match action(&v, v.tag(), to_trim) { Ok(out) => ReturnSuccess::value(out), Err(err) => Err(err), } @@ -72,7 +89,7 @@ async fn operate( for path in &column_paths { let swapping = ret.swap_data_by_column_path( path, - Box::new(move |old| action(old, old.tag())), + Box::new(move |old| action(old, old.tag(), to_trim)), ); match swapping { @@ -91,11 +108,15 @@ async fn operate( .to_output_stream()) } -fn action(input: &Value, tag: impl Into) -> Result { +fn action(input: &Value, tag: impl Into, char_: Option) -> Result { match &input.value { UntaggedValue::Primitive(Primitive::Line(s)) | UntaggedValue::Primitive(Primitive::String(s)) => { - Ok(UntaggedValue::string(s.trim()).into_value(tag)) + Ok(UntaggedValue::string(match char_ { + None => String::from(s.trim()), + Some(ch) => trim_char(s, ch, true, true), + }) + .into_value(tag)) } other => { let got = format!("got {}", other.type_name()); @@ -108,12 +129,11 @@ fn action(input: &Value, tag: impl Into) -> Result { } } -// TODO make callable using flag -pub fn trim_char(s: String, to_trim: char, leading: bool, trailing: bool) -> String { +pub fn trim_char(from: &str, to_trim: char, leading: bool, trailing: bool) -> String { let mut trimmed = String::from(""); let mut backlog = String::from(""); let mut at_left = true; - s.chars().for_each(|ch| match ch { + from.chars().for_each(|ch| match ch { c if c == to_trim => { if !(leading && at_left) { if trailing { @@ -154,7 +174,7 @@ mod tests { let word = string("andres "); let expected = string("andres"); - let actual = action(&word, Tag::unknown()).unwrap(); + let actual = action(&word, Tag::unknown(), None).unwrap(); assert_eq!(actual, expected); } } diff --git a/crates/nu-cli/tests/commands/str_.rs b/crates/nu-cli/tests/commands/str_.rs index 61d18ffbf5..62031a22cd 100644 --- a/crates/nu-cli/tests/commands/str_.rs +++ b/crates/nu-cli/tests/commands/str_.rs @@ -22,6 +22,19 @@ fn trims() { }) } +#[test] +fn error_trim_multiple_chars() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 'does it work now?!' | str trim -c '?!' + "# + ) + ); + + assert!(actual.err.contains("char")); +} + #[test] fn capitalizes() { Playground::setup("str_test_2", |dirs, sandbox| {