diff --git a/crates/nu-command/src/strings/split/column.rs b/crates/nu-command/src/strings/split/column.rs index 4a9a96a8d7..3ed56a56ae 100644 --- a/crates/nu-command/src/strings/split/column.rs +++ b/crates/nu-command/src/strings/split/column.rs @@ -26,6 +26,12 @@ impl Command for SubCommand { "The character or string that denotes what separates columns.", ) .switch("collapse-empty", "remove empty columns", Some('c')) + .named( + "number", + SyntaxShape::Int, + "Split into maximum number of items", + Some('n'), + ) .switch("regex", "separator is a regular expression", Some('r')) .rest( "rest", @@ -91,6 +97,20 @@ impl Command for SubCommand { }), ])), }, + Example { + description: "Split into columns, last column may contain the delimiter", + example: r"['author: Salina Yoon' r#'title: Where's Ellie?: A Hide-and-Seek Book'#] | split column --number 2 ': ' key value", + result: Some(Value::test_list(vec![ + Value::test_record(record! { + "key" => Value::test_string("author"), + "value" => Value::test_string("Salina Yoon"), + }), + Value::test_record(record! { + "key" => Value::test_string("title"), + "value" => Value::test_string("Where's Ellie?: A Hide-and-Seek Book"), + }), + ])), + }, ] } @@ -108,12 +128,14 @@ impl Command for SubCommand { let separator: Spanned = call.req(engine_state, stack, 0)?; let rest: Vec> = call.rest(engine_state, stack, 1)?; let collapse_empty = call.has_flag(engine_state, stack, "collapse-empty")?; + let max_split: Option = call.get_flag(engine_state, stack, "number")?; let has_regex = call.has_flag(engine_state, stack, "regex")?; let args = Arguments { separator, rest, collapse_empty, + max_split, has_regex, }; split_column(engine_state, call, input, args) @@ -128,12 +150,14 @@ impl Command for SubCommand { let separator: Spanned = call.req_const(working_set, 0)?; let rest: Vec> = call.rest_const(working_set, 1)?; let collapse_empty = call.has_flag_const(working_set, "collapse-empty")?; + let max_split: Option = call.get_flag_const(working_set, "number")?; let has_regex = call.has_flag_const(working_set, "regex")?; let args = Arguments { separator, rest, collapse_empty, + max_split, has_regex, }; split_column(working_set.permanent(), call, input, args) @@ -144,6 +168,7 @@ struct Arguments { separator: Spanned, rest: Vec>, collapse_empty: bool, + max_split: Option, has_regex: bool, } @@ -169,7 +194,16 @@ fn split_column( })?; input.flat_map( - move |x| split_column_helper(&x, ®ex, &args.rest, args.collapse_empty, name_span), + move |x| { + split_column_helper( + &x, + ®ex, + &args.rest, + args.collapse_empty, + args.max_split, + name_span, + ) + }, engine_state.signals(), ) } @@ -179,13 +213,20 @@ fn split_column_helper( separator: &Regex, rest: &[Spanned], collapse_empty: bool, + max_split: Option, head: Span, ) -> Vec { if let Ok(s) = v.coerce_str() { - let split_result: Vec<_> = separator - .split(&s) - .filter(|x| !(collapse_empty && x.is_empty())) - .collect(); + let split_result: Vec<_> = match max_split { + Some(max_split) => separator + .splitn(&s, max_split) + .filter(|x| !(collapse_empty && x.is_empty())) + .collect(), + None => separator + .split(&s) + .filter(|x| !(collapse_empty && x.is_empty())) + .collect(), + }; let positional: Vec<_> = rest.iter().map(|f| f.item.clone()).collect(); // If they didn't provide column names, make up our own diff --git a/crates/nu-command/tests/commands/split_column.rs b/crates/nu-command/tests/commands/split_column.rs index 544c238cfa..5b40117990 100644 --- a/crates/nu-command/tests/commands/split_column.rs +++ b/crates/nu-command/tests/commands/split_column.rs @@ -33,6 +33,19 @@ fn to_column() { assert!(actual.out.contains("shipper")); + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.txt + | lines + | str trim + | split column -n 3 "," + | get column3 + "# + )); + + assert!(actual.out.contains("tariff_item,name,origin")); + let actual = nu!( cwd: dirs.test(), pipeline( r"