diff --git a/crates/nu-command/src/commands.rs b/crates/nu-command/src/commands.rs index 487395e78..a59edf6bf 100644 --- a/crates/nu-command/src/commands.rs +++ b/crates/nu-command/src/commands.rs @@ -98,6 +98,7 @@ pub(crate) mod reject; pub(crate) mod rename; pub(crate) mod reverse; pub(crate) mod rm; +pub(crate) mod rotate; pub(crate) mod run_external; pub(crate) mod save; pub(crate) mod select; @@ -244,6 +245,7 @@ pub(crate) use reject::Reject; pub(crate) use rename::Rename; pub(crate) use reverse::Reverse; pub(crate) use rm::Remove; +pub(crate) use rotate::{Rotate, RotateCounterClockwise}; pub(crate) use run_external::RunExternalCommand; pub(crate) use save::Save; pub(crate) use select::Command as Select; diff --git a/crates/nu-command/src/commands/default_context.rs b/crates/nu-command/src/commands/default_context.rs index 2531a1ec0..ddd947e98 100644 --- a/crates/nu-command/src/commands/default_context.rs +++ b/crates/nu-command/src/commands/default_context.rs @@ -161,6 +161,8 @@ pub fn create_default_context(interactive: bool) -> Result>, +} + +#[async_trait] +impl WholeStreamCommand for Command { + fn name(&self) -> &str { + "rotate" + } + + fn signature(&self) -> Signature { + Signature::build("rotate").rest( + SyntaxShape::String, + "the names to give columns once rotated", + ) + } + + fn usage(&self) -> &str { + "Rotates the table by 90 degrees clockwise." + } + + async fn run(&self, args: CommandArgs) -> Result { + rotate(args).await + } +} + +pub async fn rotate(args: CommandArgs) -> Result { + let name = args.call_info.name_tag.clone(); + let (Arguments { rest }, input) = args.process().await?; + + let input = input.into_vec().await; + let total_rows = input.len(); + let descs = merge_descriptors(&input); + let total_descriptors = descs.len(); + + let descs = descs.into_iter().rev().collect::>(); + + if total_rows == 0 { + return Ok(OutputStream::empty()); + } + + let mut headers: Vec = vec![]; + + for i in 0..=total_rows { + headers.push(format!("Column{}", i)); + } + + let first = input[0].clone(); + + let name = if first.tag.anchor().is_some() { + first.tag + } else { + name + }; + + let values = + UntaggedValue::table(&input.into_iter().rev().collect::>()).into_value(&name); + + let values = nu_data::utils::group( + &values, + &Some(Box::new(move |row_number: usize, _| { + Ok(match headers.get(row_number) { + Some(name) => name.clone(), + None => String::new(), + }) + })), + &name, + )?; + + Ok(futures::stream::iter( + (0..total_descriptors) + .map(move |row_number| { + let mut row = TaggedDictBuilder::new(&name); + + for (current_numbered_column, (column_name, _)) in values.row_entries().enumerate() + { + let raw_column_path = + format!("{}.0.{}", column_name, descs[row_number]).spanned_unknown(); + let path = ColumnPath::build(&raw_column_path); + + match &values.get_data_by_column_path(&path, Box::new(move |_, _, error| error)) + { + Ok(x) => { + row.insert_value( + rest.get(current_numbered_column) + .map(|c| c.item.clone()) + .unwrap_or_else(|| column_name.to_string()), + x.clone(), + ); + } + Err(_) => {} + } + } + + row.insert_value( + rest.get(total_rows) + .map(|c| c.item.clone()) + .unwrap_or_else(|| format!("Column{}", total_rows)), + UntaggedValue::string(&descs[row_number]).into_untagged_value(), + ); + + ReturnSuccess::value(row.into_value()) + }) + .rev() + .collect::>(), + ) + .to_output_stream()) +} diff --git a/crates/nu-command/src/commands/rotate/counter_clockwise.rs b/crates/nu-command/src/commands/rotate/counter_clockwise.rs new file mode 100644 index 000000000..64f123317 --- /dev/null +++ b/crates/nu-command/src/commands/rotate/counter_clockwise.rs @@ -0,0 +1,117 @@ +use crate::prelude::*; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{ + merge_descriptors, ColumnPath, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, + UntaggedValue, +}; +use nu_source::{SpannedItem, Tagged}; +use nu_value_ext::ValueExt; + +pub struct SubCommand; + +#[derive(Deserialize)] +pub struct Arguments { + rest: Vec>, +} + +#[async_trait] +impl WholeStreamCommand for SubCommand { + fn name(&self) -> &str { + "rotate counter-clockwise" + } + + fn signature(&self) -> Signature { + Signature::build("rotate counter-clockwise").rest( + SyntaxShape::String, + "the names to give columns once rotated", + ) + } + + fn usage(&self) -> &str { + "Rotates the table by 90 degrees counter clockwise." + } + + async fn run(&self, args: CommandArgs) -> Result { + rotate(args).await + } +} + +pub async fn rotate(args: CommandArgs) -> Result { + let name = args.call_info.name_tag.clone(); + let (Arguments { rest }, input) = args.process().await?; + + let input = input.into_vec().await; + let descs = merge_descriptors(&input); + let total_rows = input.len(); + + if total_rows == 0 { + return Ok(OutputStream::empty()); + } + + let mut headers: Vec = vec![]; + for i in 0..=total_rows { + headers.push(format!("Column{}", i + 1)); + } + + let first = input[0].clone(); + + let name = if first.tag.anchor().is_some() { + first.tag + } else { + name + }; + + let values = UntaggedValue::table(&input).into_value(&name); + + let values = nu_data::utils::group( + &values, + &Some(Box::new(move |row_number: usize, _| { + Ok(match headers.get(row_number) { + Some(name) => name.clone(), + None => String::new(), + }) + })), + &name, + )?; + + Ok(futures::stream::iter( + (0..descs.len()) + .rev() + .map(move |row_number| { + let mut row = TaggedDictBuilder::new(&name); + + row.insert_value( + rest.get(0) + .map(|c| c.item.clone()) + .unwrap_or_else(|| String::from("Column0")), + UntaggedValue::string(descs.get(row_number).unwrap_or(&String::new())) + .into_untagged_value(), + ); + + for (current_numbered_column, (column_name, _)) in values.row_entries().enumerate() + { + let raw_column_path = + format!("{}.0.{}", column_name, &descs[row_number]).spanned_unknown(); + let path = ColumnPath::build(&raw_column_path); + + match &values.get_data_by_column_path(&path, Box::new(move |_, _, error| error)) + { + Ok(x) => { + row.insert_value( + rest.get(current_numbered_column + 1) + .map(|c| c.item.clone()) + .unwrap_or_else(|| column_name.to_string()), + x.clone(), + ); + } + Err(_) => {} + } + } + + ReturnSuccess::value(row.into_value()) + }) + .collect::>(), + ) + .to_output_stream()) +} diff --git a/crates/nu-command/src/commands/rotate/mod.rs b/crates/nu-command/src/commands/rotate/mod.rs new file mode 100644 index 000000000..16b4d8c06 --- /dev/null +++ b/crates/nu-command/src/commands/rotate/mod.rs @@ -0,0 +1,5 @@ +mod command; +mod counter_clockwise; + +pub use command::Command as Rotate; +pub use counter_clockwise::SubCommand as RotateCounterClockwise; diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index 70123f556..241821207 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -43,6 +43,7 @@ mod reduce; mod rename; mod reverse; mod rm; +mod rotate; mod save; mod select; mod semicolon; diff --git a/crates/nu-command/tests/commands/rotate.rs b/crates/nu-command/tests/commands/rotate.rs new file mode 100644 index 000000000..0432d89d5 --- /dev/null +++ b/crates/nu-command/tests/commands/rotate.rs @@ -0,0 +1,83 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn counter_clockwise() { + let table = pipeline( + r#" + echo [ + [col1, col2, EXPECTED]; + + [---, "|||", XX1] + [---, "|||", XX2] + [---, "|||", XX3] + ] + "#, + ); + + let expected = nu!(cwd: ".", pipeline( + r#" + echo [ + [ Column0, Column1, Column2, Column3]; + + [ EXPECTED, XX1, XX2, XX3] + [ col2, "|||", "|||", "|||"] + [ col1, ---, ---, ---] + ] + | where Column0 == EXPECTED + | get Column1 Column2 Column3 + | str collect "-" + "#, + )); + + let actual = nu!( + cwd: ".", + format!("{} | {}", table, pipeline(r#" + rotate counter-clockwise + | where Column0 == EXPECTED + | get Column1 Column2 Column3 + | str collect "-" + "#))); + + assert_eq!(actual.out, expected.out); +} + +#[test] +fn clockwise() { + let table = pipeline( + r#" + echo [ + [col1, col2, EXPECTED]; + + [ ---, "|||", XX1] + [ ---, "|||", XX2] + [ ---, "|||", XX3] + ] + "#, + ); + + let expected = nu!(cwd: ".", pipeline( + r#" + echo [ + [ Column0, Column1, Column2, Column3]; + + [ ---, ---, ---, col1] + [ "|||", "|||", "|||", col2] + [ XX3, XX2, XX1, EXPECTED] + ] + | where Column3 == EXPECTED + | get Column0 Column1 Column2 + | str collect "-" + "#, + )); + + let actual = nu!( + cwd: ".", + format!("{} | {}", table, pipeline(r#" + rotate + | where Column3 == EXPECTED + | get Column0 Column1 Column2 + | str collect "-" + "#))); + + assert_eq!(actual.out, expected.out); +}