diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index a7c0cc4b93..5b9ec0238b 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -148,6 +148,7 @@ pub fn create_default_context() -> EngineState { StrStartsWith, StrReverse, StrSubstring, + StrTrim, Sys, Table, To, diff --git a/crates/nu-command/src/strings/str_/mod.rs b/crates/nu-command/src/strings/str_/mod.rs index d1d2143559..ca32976905 100644 --- a/crates/nu-command/src/strings/str_/mod.rs +++ b/crates/nu-command/src/strings/str_/mod.rs @@ -12,6 +12,7 @@ mod reverse; mod rpad; mod starts_with; mod substring; +mod trim; pub use capitalize::SubCommand as StrCapitalize; pub use case::*; @@ -27,3 +28,4 @@ pub use reverse::SubCommand as StrReverse; pub use rpad::SubCommand as StrRpad; pub use starts_with::SubCommand as StrStartsWith; pub use substring::SubCommand as StrSubstring; +pub use trim::Trim as StrTrim; diff --git a/crates/nu-command/src/strings/str_/trim/command.rs b/crates/nu-command/src/strings/str_/trim/command.rs new file mode 100644 index 0000000000..ef19899600 --- /dev/null +++ b/crates/nu-command/src/strings/str_/trim/command.rs @@ -0,0 +1,1160 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::{Call, CellPath}, + engine::{Command, EngineState, Stack}, + Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; +use std::sync::Arc; + +#[derive(Clone)] +pub struct SubCommand; + +struct Arguments { + character: Option, + column_paths: Vec, +} + +#[derive(Default, Debug, Copy, Clone)] +pub struct ClosureFlags { + all_flag: bool, + left_trim: bool, + right_trim: bool, + format_flag: bool, + both_flag: bool, +} + +impl Command for SubCommand { + fn name(&self) -> &str { + "str trim" + } + + fn signature(&self) -> Signature { + Signature::build("str trim") + .rest( + "rest", + SyntaxShape::CellPath, + "optionally trim text by column paths", + ) + .named( + "char", + SyntaxShape::String, + "character to trim (default: whitespace)", + Some('c'), + ) + .switch( + "left", + "trims characters only from the beginning of the string (default: whitespace)", + Some('l'), + ) + .switch( + "right", + "trims characters only from the end of the string (default: whitespace)", + Some('r'), + ) + .switch( + "all", + "trims all characters from both sides of the string *and* in the middle (default: whitespace)", + Some('a'), + ) + .switch("both", "trims all characters from left and right side of the string (default: whitespace)", Some('b')) + .switch("format", "trims spaces replacing multiple characters with singles in the middle (default: whitespace)", Some('f')) + } + fn usage(&self) -> &str { + "trims text" + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + operate(engine_state, stack, call, input, &trim) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Trim whitespace", + example: "'Nu shell ' | str trim", + result: Some(Value::test_string("Nu shell")), + }, + Example { + description: "Trim a specific character", + example: "'=== Nu shell ===' | str trim -c '=' | str trim", + result: Some(Value::test_string("Nu shell")), + }, + Example { + description: "Trim all characters", + example: "' Nu shell ' | str trim -a", + result: Some(Value::test_string("Nushell")), + }, + Example { + description: "Trim whitespace from the beginning of string", + example: "' Nu shell ' | str trim -l", + result: Some(Value::test_string("Nu shell ")), + }, + Example { + description: "Trim a specific character", + example: "'=== Nu shell ===' | str trim -c '='", + result: Some(Value::test_string(" Nu shell ")), + }, + Example { + description: "Trim whitespace from the end of string", + example: "' Nu shell ' | str trim -r", + result: Some(Value::test_string(" Nu shell")), + }, + Example { + description: "Trim a specific character", + example: "'=== Nu shell ===' | str trim -r -c '='", + result: Some(Value::test_string("=== Nu shell ")), + }, + ] + } +} + +pub fn operate( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + trim_operation: &'static F, +) -> Result +where + F: Fn(&str, Option, &ClosureFlags) -> String + Send + Sync + 'static, +{ + let head = call.head; + let (options, closure_flags, input) = ( + Arc::new(Arguments { + character: call.get_flag(engine_state, stack, "char")?, + column_paths: call.rest(engine_state, stack, 0)?, + }), + ClosureFlags { + all_flag: call.has_flag("all"), + left_trim: call.has_flag("left"), + right_trim: call.has_flag("right"), + format_flag: call.has_flag("format"), + both_flag: call.has_flag("both") + || (!call.has_flag("all") + && !call.has_flag("left") + && !call.has_flag("right") + && !call.has_flag("format")), // this is the case if no flags are provided + }, + input, + ); + let to_trim = match options.character.as_ref() { + Some(v) => v.as_string().unwrap().chars().next(), + None => None, + }; + + input.map( + move |v| { + if options.column_paths.is_empty() { + action( + &v, + head, + to_trim, + &closure_flags, + &trim_operation, + ActionMode::Global, + ) + } else { + let mut ret = v; + for path in &options.column_paths { + let r = ret.update_cell_path( + &path.members, + Box::new(move |old| { + action( + old, + head, + to_trim, + &closure_flags, + &trim_operation, + ActionMode::Local, + ) + }), + ); + if let Err(error) = r { + return Value::Error { error }; + } + } + ret + } + }, + engine_state.ctrlc.clone(), + ) +} + +#[derive(Debug, Copy, Clone)] +pub enum ActionMode { + Local, + Global, +} + +pub fn action( + input: &Value, + head: Span, + char_: Option, + closure_flags: &ClosureFlags, + trim_operation: &F, + mode: ActionMode, +) -> Value +where + F: Fn(&str, Option, &ClosureFlags) -> String + Send + Sync + 'static, +{ + match input { + Value::String { val: s, .. } => Value::String { + val: trim_operation(s, char_, closure_flags), + span: head, + }, + other => match mode { + ActionMode::Global => match other { + Value::Record { cols, vals, span } => { + let new_vals = vals + .iter() + .map(|v| action(v, head, char_, closure_flags, trim_operation, mode)) + .collect(); + + Value::Record { + cols: cols.to_vec(), + vals: new_vals, + span: *span, + } + } + Value::List { vals, span } => { + let new_vals = vals + .iter() + .map(|v| action(v, head, char_, closure_flags, trim_operation, mode)) + .collect(); + + Value::List { + vals: new_vals, + span: *span, + } + } + _ => input.clone(), + }, + ActionMode::Local => { + let got = format!("Input must be a string. Found {}", other.get_type()); + Value::Error { + error: ShellError::UnsupportedInput(got, head), + } + } + }, + } +} + +fn trim(s: &str, char_: Option, closure_flags: &ClosureFlags) -> String { + let ClosureFlags { + left_trim, + right_trim, + all_flag, + both_flag, + format_flag, + } = closure_flags; + let delimiters = match char_ { + Some(c) => vec![c], + // Trying to make this trim work like rust default trim() + // which uses is_whitespace() as a default + None => vec![ + ' ', // space + '\x09', // horizontal tab + '\x0A', // new line, line feed + '\x0B', // vertical tab + '\x0C', // form feed, new page + '\x0D', // carriage return + ], //whitespace + }; + + if *left_trim { + s.trim_start_matches(&delimiters[..]).to_string() + } else if *right_trim { + s.trim_end_matches(&delimiters[..]).to_string() + } else if *all_flag { + s.split(&delimiters[..]) + .filter(|s| !s.is_empty()) + .collect::() + } else if *both_flag { + s.trim_matches(&delimiters[..]).to_string() + } else if *format_flag { + // The idea here is to use regex to go through these delimiters and + // where there are multiple, replace them with singles + + // create our return string which is a copy of the original string + let mut return_string = String::from(s); + // Iterate through the delimiters replacing them with regex friendly names + for r in &delimiters { + let reg = match r { + ' ' => r"\s".to_string(), + '\x09' => r"\t".to_string(), + '\x0A' => r"\n".to_string(), + '\x0B' => r"\v".to_string(), + '\x0C' => r"\f".to_string(), + '\x0D' => r"\r".to_string(), + _ => format!(r"\{}", r), + }; + // create a regex string that looks for 2 or more of each of these characters + let re_str = format!("{}{{2,}}", reg); + // create the regex + let re = regex::Regex::new(&re_str).expect("Error creating regular expression"); + // replace all mutliple occurances with single occurences represented by r + let new_str = re.replace_all(&return_string, r.to_string()); + // update the return string so the next loop has the latest changes + return_string = new_str.to_string(); + } + // for good measure, trim_matches, which gets the start and end + // theoretically we shouldn't have to do this but from my testing, we do. + return_string.trim_matches(&delimiters[..]).to_string() + } else { + s.trim().to_string() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } + + fn make_record(cols: Vec<&str>, vals: Vec<&str>) -> Value { + Value::Record { + cols: cols.iter().map(|x| x.to_string()).collect(), + vals: vals + .iter() + .map(|x| Value::String { + val: x.to_string(), + span: Span::unknown(), + }) + .collect(), + span: Span::unknown(), + } + } + + fn make_list(vals: Vec<&str>) -> Value { + Value::List { + vals: vals + .iter() + .map(|x| Value::String { + val: x.to_string(), + span: Span::unknown(), + }) + .collect(), + span: Span::unknown(), + } + } + + #[test] + fn trims() { + let word = Value::test_string("andres "); + let expected = Value::test_string("andres"); + let closure_flags = ClosureFlags { + both_flag: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Local, + ); + assert_eq!(actual, expected); + } + + #[test] + fn trims_global() { + let word = Value::test_string(" global "); + let expected = Value::test_string("global"); + let closure_flags = ClosureFlags { + both_flag: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trim_ignores_numbers() { + let number = Value::test_int(2020); + let expected = Value::test_int(2020); + let closure_flags = ClosureFlags { + both_flag: true, + ..Default::default() + }; + + let actual = action( + &number, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trim_row() { + let row = make_record(vec!["a", "b"], vec![" c ", " d "]); + // ["a".to_string() => string(" c "), " b ".to_string() => string(" d ")]; + let expected = make_record(vec!["a", "b"], vec!["c", "d"]); + let closure_flags = ClosureFlags { + both_flag: true, + ..Default::default() + }; + + let actual = action( + &row, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trim_table() { + let row = make_list(vec![" a ", "d"]); + let expected = make_list(vec!["a", "d"]); + let closure_flags = ClosureFlags { + both_flag: true, + ..Default::default() + }; + + let actual = action( + &row, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn trims_custom_character_both_ends() { + let word = Value::test_string("!#andres#!"); + let expected = Value::test_string("#andres#"); + let closure_flags = ClosureFlags { + both_flag: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + Some('!'), + &closure_flags, + &trim, + ActionMode::Local, + ); + assert_eq!(actual, expected); + } + #[test] + fn trims_all_white_space() { + let word = Value::test_string(" Value1 a lot of spaces "); + let expected = Value::test_string("Value1alotofspaces"); + let closure_flags = ClosureFlags { + all_flag: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + Some(' '), + &closure_flags, + &trim, + ActionMode::Local, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trims_row_all_white_space() { + let row = make_record( + vec!["a", "b"], + vec![" nu shell ", " b c d e "], + ); + let expected = make_record(vec!["a", "b"], vec!["nushell", "bcde"]); + + let closure_flags = ClosureFlags { + all_flag: true, + ..Default::default() + }; + + let actual = action( + &row, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trims_table_all_white_space() { + let row = Value::List { + vals: vec![ + Value::String { + val: " nu shell ".to_string(), + span: Span::unknown(), + }, + Value::Int { + val: 65, + span: Span::unknown(), + }, + Value::String { + val: " d".to_string(), + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + let expected = Value::List { + vals: vec![ + Value::String { + val: "nushell".to_string(), + span: Span::unknown(), + }, + Value::Int { + val: 65, + span: Span::unknown(), + }, + Value::String { + val: "d".to_string(), + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + + let closure_flags = ClosureFlags { + all_flag: true, + ..Default::default() + }; + + let actual = action( + &row, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn trims_all_custom_character() { + let word = Value::test_string(".Value1.a.lot..of...dots."); + let expected = Value::test_string("Value1alotofdots"); + let closure_flags = ClosureFlags { + all_flag: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + Some('.'), + &closure_flags, + &trim, + ActionMode::Local, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trims_row_all_custom_character() { + let row = make_record(vec!["a", "b"], vec!["!!!!nu!!shell!!!", "!!b!c!!d!e!!"]); + let expected = make_record(vec!["a", "b"], vec!["nushell", "bcde"]); + let closure_flags = ClosureFlags { + all_flag: true, + ..Default::default() + }; + + let actual = action( + &row, + Span::unknown(), + Some('!'), + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trims_table_all_custom_character() { + let row = Value::List { + vals: vec![ + Value::String { + val: "##nu####shell##".to_string(), + span: Span::unknown(), + }, + Value::Int { + val: 65, + span: Span::unknown(), + }, + Value::String { + val: "#d".to_string(), + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + let expected = Value::List { + vals: vec![ + Value::String { + val: "nushell".to_string(), + span: Span::unknown(), + }, + Value::Int { + val: 65, + span: Span::unknown(), + }, + Value::String { + val: "d".to_string(), + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + + let closure_flags = ClosureFlags { + all_flag: true, + ..Default::default() + }; + + let actual = action( + &row, + Span::unknown(), + Some('#'), + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn trims_whitespace_from_left() { + let word = Value::test_string(" andres "); + let expected = Value::test_string("andres "); + let closure_flags = ClosureFlags { + left_trim: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Local, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trim_left_ignores_numbers() { + let number = Value::test_int(2020); + let expected = Value::test_int(2020); + let closure_flags = ClosureFlags { + left_trim: true, + ..Default::default() + }; + + let actual = action( + &number, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn trims_left_global() { + let word = Value::test_string(" global "); + let expected = Value::test_string("global "); + let closure_flags = ClosureFlags { + left_trim: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trim_left_row() { + let row = make_record(vec!["a", "b"], vec![" c ", " d "]); + let expected = make_record(vec!["a", "b"], vec!["c ", "d "]); + let closure_flags = ClosureFlags { + left_trim: true, + ..Default::default() + }; + + let actual = action( + &row, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trim_left_table() { + let row = Value::List { + vals: vec![ + Value::String { + val: " a ".to_string(), + span: Span::unknown(), + }, + Value::Int { + val: 65, + span: Span::unknown(), + }, + Value::String { + val: " d".to_string(), + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + let expected = Value::List { + vals: vec![ + Value::String { + val: "a ".to_string(), + span: Span::unknown(), + }, + Value::Int { + val: 65, + span: Span::unknown(), + }, + Value::String { + val: "d".to_string(), + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + + let closure_flags = ClosureFlags { + left_trim: true, + ..Default::default() + }; + + let actual = action( + &row, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn trims_custom_chars_from_left() { + let word = Value::test_string("!!! andres !!!"); + let expected = Value::test_string(" andres !!!"); + let closure_flags = ClosureFlags { + left_trim: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + Some('!'), + &closure_flags, + &trim, + ActionMode::Local, + ); + assert_eq!(actual, expected); + } + #[test] + fn trims_whitespace_from_right() { + let word = Value::test_string(" andres "); + let expected = Value::test_string(" andres"); + let closure_flags = ClosureFlags { + right_trim: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Local, + ); + assert_eq!(actual, expected); + } + + #[test] + fn trims_right_global() { + let word = Value::test_string(" global "); + let expected = Value::test_string(" global"); + let closure_flags = ClosureFlags { + right_trim: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trim_right_ignores_numbers() { + let number = Value::test_int(2020); + let expected = Value::test_int(2020); + let closure_flags = ClosureFlags { + right_trim: true, + ..Default::default() + }; + + let actual = action( + &number, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trim_right_row() { + let row = make_record(vec!["a", "b"], vec![" c ", " d "]); + let expected = make_record(vec!["a", "b"], vec![" c", " d"]); + let closure_flags = ClosureFlags { + right_trim: true, + ..Default::default() + }; + + let actual = action( + &row, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trim_right_table() { + let row = Value::List { + vals: vec![ + Value::String { + val: " a ".to_string(), + span: Span::unknown(), + }, + Value::Int { + val: 65, + span: Span::unknown(), + }, + Value::String { + val: " d".to_string(), + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + let expected = Value::List { + vals: vec![ + Value::String { + val: " a".to_string(), + span: Span::unknown(), + }, + Value::Int { + val: 65, + span: Span::unknown(), + }, + Value::String { + val: " d".to_string(), + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + + let closure_flags = ClosureFlags { + right_trim: true, + ..Default::default() + }; + + let actual = action( + &row, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn trims_custom_chars_from_right() { + let word = Value::test_string("#@! andres !@#"); + let expected = Value::test_string("#@! andres !@"); + let closure_flags = ClosureFlags { + right_trim: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + Some('#'), + &closure_flags, + &trim, + ActionMode::Local, + ); + assert_eq!(actual, expected); + } + + #[test] + fn trims_whitespace_format_flag() { + let word = Value::test_string(" nushell is great "); + let expected = Value::test_string("nushell is great"); + let closure_flags = ClosureFlags { + format_flag: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + Some(' '), + &closure_flags, + &trim, + ActionMode::Local, + ); + assert_eq!(actual, expected); + } + + #[test] + fn trims_format_flag_global() { + let word = Value::test_string("global "); + let expected = Value::test_string("global"); + let closure_flags = ClosureFlags { + format_flag: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + Some(' '), + &closure_flags, + &trim, + ActionMode::Local, + ); + assert_eq!(actual, expected); + } + #[test] + fn global_trim_format_flag_ignores_numbers() { + let number = Value::test_int(2020); + let expected = Value::test_int(2020); + let closure_flags = ClosureFlags { + format_flag: true, + ..Default::default() + }; + + let actual = action( + &number, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trim_format_flag_row() { + let row = make_record(vec!["a", "b"], vec![" c ", " b c d e "]); + let expected = make_record(vec!["a", "b"], vec!["c", "b c d e"]); + let closure_flags = ClosureFlags { + format_flag: true, + ..Default::default() + }; + + let actual = action( + &row, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn global_trim_format_flag_table() { + let row = Value::List { + vals: vec![ + Value::String { + val: " a b c d ".to_string(), + span: Span::unknown(), + }, + Value::Int { + val: 65, + span: Span::unknown(), + }, + Value::String { + val: " b c d e f".to_string(), + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + let expected = Value::List { + vals: vec![ + Value::String { + val: "a b c d".to_string(), + span: Span::unknown(), + }, + Value::Int { + val: 65, + span: Span::unknown(), + }, + Value::String { + val: "b c d e f".to_string(), + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + + let closure_flags = ClosureFlags { + format_flag: true, + ..Default::default() + }; + + let actual = action( + &row, + Span::unknown(), + None, + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } + + #[test] + fn trims_custom_chars_format_flag() { + let word = Value::test_string(".Value1.a..lot...of....dots."); + let expected = Value::test_string("Value1.a.lot.of.dots"); + let closure_flags = ClosureFlags { + format_flag: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + Some('.'), + &closure_flags, + &trim, + ActionMode::Local, + ); + assert_eq!(actual, expected); + } + + #[test] + fn trims_all_format_flag_whitespace() { + let word = Value::test_string(" nushell is great "); + let expected = Value::test_string("nushellisgreat"); + let closure_flags = ClosureFlags { + format_flag: true, + all_flag: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + Some(' '), + &closure_flags, + &trim, + ActionMode::Local, + ); + assert_eq!(actual, expected); + } + + #[test] + fn trims_all_format_flag_global() { + let word = Value::test_string(" nushell is great "); + let expected = Value::test_string("nushellisgreat"); + let closure_flags = ClosureFlags { + format_flag: true, + all_flag: true, + ..Default::default() + }; + + let actual = action( + &word, + Span::unknown(), + Some(' '), + &closure_flags, + &trim, + ActionMode::Global, + ); + assert_eq!(actual, expected); + } +} diff --git a/crates/nu-command/src/strings/str_/trim/mod.rs b/crates/nu-command/src/strings/str_/trim/mod.rs new file mode 100644 index 0000000000..6ef5d04617 --- /dev/null +++ b/crates/nu-command/src/strings/str_/trim/mod.rs @@ -0,0 +1,2 @@ +mod command; +pub use command::SubCommand as Trim;