diff --git a/crates/nu-command/src/strings/split/column.rs b/crates/nu-command/src/strings/split/column.rs new file mode 100644 index 0000000000..fb3836ee3e --- /dev/null +++ b/crates/nu-command/src/strings/split/column.rs @@ -0,0 +1,164 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EvaluationContext}, + IntoValueStream, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, +}; + +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "split column" + } + + fn signature(&self) -> Signature { + Signature::build("split column") + .required( + "separator", + SyntaxShape::String, + "the character that denotes what separates columns", + ) + .switch("collapse-empty", "remove empty columns", Some('c')) + .rest( + "rest", + SyntaxShape::String, + "column names to give the new columns", + ) + } + + fn usage(&self) -> &str { + "splits contents across multiple columns via the separator." + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + split_column(context, call, input) + } +} + +fn split_column( + context: &EvaluationContext, + call: &Call, + input: Value, +) -> Result { + let name_span = call.head; + let separator: Spanned = call.req(context, 0)?; + let rest: Vec> = call.rest(context, 1)?; + let collapse_empty = call.has_flag("collapse-empty"); + + Ok(match input { + Value::List { vals, span } => Value::List { + vals: vals + .iter() + .map(move |x| split_column_helper(x, &separator, &rest, collapse_empty, name_span)) + .collect(), + span, + }, + Value::Stream { stream, span } => Value::Stream { + stream: stream + .map(move |x| split_column_helper(&x, &separator, &rest, collapse_empty, name_span)) + .into_value_stream(), + span, + }, + v => { + if v.as_string().is_ok() { + Value::List { + vals: vec![split_column_helper( + &v, + &separator, + &rest, + collapse_empty, + name_span, + )], + span: call.head, + } + } else { + Value::Error { + error: ShellError::PipelineMismatch { + expected: Type::String, + expected_span: call.head, + origin: v.span(), + }, + } + } + } + }) +} + +fn split_column_helper( + v: &Value, + separator: &Spanned, + rest: &[Spanned], + collapse_empty: bool, + head: Span, +) -> Value { + if let Ok(s) = v.as_string() { + let splitter = separator.item.replace("\\n", "\n"); + + let split_result: Vec<_> = if collapse_empty { + s.split(&splitter).filter(|s| !s.is_empty()).collect() + } else { + s.split(&splitter).collect() + }; + + let positional: Vec<_> = rest.iter().map(|f| f.item.clone()).collect(); + + // If they didn't provide column names, make up our own + + let mut cols = vec![]; + let mut vals = vec![]; + if positional.is_empty() { + let mut gen_columns = vec![]; + for i in 0..split_result.len() { + gen_columns.push(format!("Column{}", i + 1)); + } + + for (&k, v) in split_result.iter().zip(&gen_columns) { + cols.push(v.to_string()); + vals.push(Value::String { + val: k.into(), + span: head, + }); + } + } else { + for (&k, v) in split_result.iter().zip(&positional) { + cols.push(v.into()); + vals.push(Value::String { + val: k.into(), + span: head, + }) + } + } + Value::Record { + cols, + vals, + span: head, + } + } else { + Value::Error { + error: ShellError::PipelineMismatch { + expected: Type::String, + expected_span: head, + origin: v.span(), + }, + } + } +} + +// #[cfg(test)] +// mod tests { +// use super::ShellError; +// use super::SubCommand; + +// #[test] +// fn examples_work_as_expected() -> Result<(), ShellError> { +// use crate::examples::test as test_examples; + +// test_examples(SubCommand {}) +// } +// } diff --git a/crates/nu-command/src/strings/split/row.rs b/crates/nu-command/src/strings/split/row.rs new file mode 100644 index 0000000000..a4602e2ca4 --- /dev/null +++ b/crates/nu-command/src/strings/split/row.rs @@ -0,0 +1,116 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EvaluationContext}, + IntoValueStream, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, +}; + +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "split row" + } + + fn signature(&self) -> Signature { + Signature::build("split row").required( + "separator", + SyntaxShape::String, + "the character that denotes what separates rows", + ) + } + + fn usage(&self) -> &str { + "splits contents over multiple rows via the separator." + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + split_row(context, call, input) + } +} + +fn split_row( + context: &EvaluationContext, + call: &Call, + input: Value, +) -> Result { + let name_span = call.head; + let separator: Spanned = call.req(context, 0)?; + + Ok(match input { + Value::List { vals, span } => Value::List { + vals: vals + .iter() + .flat_map(move |x| split_row_helper(x, &separator, name_span)) + .collect(), + span, + }, + Value::Stream { stream, span } => Value::Stream { + stream: stream + .flat_map(move |x| split_row_helper(&x, &separator, name_span)) + .into_value_stream(), + span, + }, + v => { + let v_span = v.span(); + if v.as_string().is_ok() { + Value::List { + vals: split_row_helper(&v, &separator, name_span), + span: v_span, + } + } else { + Value::Error { + error: ShellError::PipelineMismatch { + expected: Type::String, + expected_span: call.head, + origin: v.span(), + }, + } + } + } + }) +} + +fn split_row_helper(v: &Value, separator: &Spanned, name: Span) -> Vec { + if let Ok(s) = v.as_string() { + let splitter = separator.item.replace("\\n", "\n"); + s.split(&splitter) + .filter_map(|s| { + if s.trim() != "" { + Some(Value::String { + val: s.into(), + span: v.span(), + }) + } else { + None + } + }) + .collect() + } else { + vec![Value::Error { + error: ShellError::PipelineMismatch { + expected: Type::String, + expected_span: name, + origin: v.span(), + }, + }] + } +} + +// #[cfg(test)] +// mod tests { +// use super::ShellError; +// use super::SubCommand; + +// #[test] +// fn examples_work_as_expected() -> Result<(), ShellError> { +// use crate::examples::test as test_examples; + +// test_examples(SubCommand {}) +// } +// } diff --git a/src/tests.rs b/src/tests.rs index 0efd0b513a..b3921acbf4 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -540,3 +540,16 @@ fn string_cell_path() -> TestResult { "c", ) } + +#[test] +fn split_row() -> TestResult { + run_test(r#""hello world" | split row " " | get 1"#, "world") +} + +#[test] +fn split_column() -> TestResult { + run_test( + r#""hello world" | split column " " | get "Column1".0"#, + "hello", + ) +}