mirror of
https://github.com/nushell/nushell
synced 2024-11-10 07:04:13 +00:00
90 degree table rotations (clockwise and counter-clockwise) (#3086)
Also for 180 degree is expected. Rotation is not exactly like pivoting (transposing) for instance, given the following table: ``` > echo [[col1, col2, col3]; [cell1, cell2, cell3] [cell4, cell5, cell6]] ───┬───────┬───────┬─────── # │ col1 │ col2 │ col3 ───┼───────┼───────┼─────── 0 │ cell1 │ cell2 │ cell3 1 │ cell4 │ cell5 │ cell6 ───┴───────┴───────┴─────── ``` To rotate it counter clockwise by 90 degrees, we can resort to first transposing (`pivot`) them adding a new column (preferably integers), sort by that column from highest to lowest, then remove the column and we have a counter clockwise rotation. ``` > echo [[col1, col2, col3]; [cell1, cell2, cell3] [cell4, cell5, cell6]] | pivot | each --numbered { = $it.item | insert idx $it.index } | sort-by idx | reverse | reject idx ───┬─────────┬─────────┬───────── # │ Column0 │ Column1 │ Column2 ───┼─────────┼─────────┼───────── 0 │ col3 │ cell3 │ cell6 1 │ col2 │ cell2 │ cell5 2 │ col1 │ cell1 │ cell4 ───┴─────────┴─────────┴───────── ``` Which we can get easily, in this case, by doing: ``` > echo [[col1, col2, cel3]; [cell1, cell2, cell3] [cell4, cell5, cell6]] | rotate counter-clockwise ───┬─────────┬─────────┬───────── # │ Column0 │ Column1 │ Column2 ───┼─────────┼─────────┼───────── 0 │ col3 │ cell3 │ cell6 1 │ col2 │ cell2 │ cell5 2 │ col1 │ cell1 │ cell4 ───┴─────────┴─────────┴───────── ``` There are also many powerful use cases with rotation, it makes a breeze creating tables with many columns, say: ``` echo 0..12 | rotate counter-clockwise | reject Column0 ───┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬──────────┬──────────┬──────────┬────────── # │ Column1 │ Column2 │ Column3 │ Column4 │ Column5 │ Column6 │ Column7 │ Column8 │ Column9 │ Column10 │ Column11 │ Column12 │ Column13 ───┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼──────────┼──────────┼──────────┼────────── 0 │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │ 12 ───┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴──────────┴──────────┴──────────┴────────── ```
This commit is contained in:
parent
42d18d2294
commit
803826cdcd
7 changed files with 331 additions and 0 deletions
|
@ -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;
|
||||
|
|
|
@ -161,6 +161,8 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||
whole_stream_command(Pivot),
|
||||
whole_stream_command(Headers),
|
||||
whole_stream_command(Reduce),
|
||||
whole_stream_command(Rotate),
|
||||
whole_stream_command(RotateCounterClockwise),
|
||||
// Data processing
|
||||
whole_stream_command(Histogram),
|
||||
whole_stream_command(Autoenv),
|
||||
|
|
121
crates/nu-command/src/commands/rotate/command.rs
Normal file
121
crates/nu-command/src/commands/rotate/command.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
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 Command;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[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<OutputStream, ShellError> {
|
||||
rotate(args).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn rotate(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
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::<Vec<_>>();
|
||||
|
||||
if total_rows == 0 {
|
||||
return Ok(OutputStream::empty());
|
||||
}
|
||||
|
||||
let mut headers: Vec<String> = 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::<Vec<_>>()).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::<Vec<_>>(),
|
||||
)
|
||||
.to_output_stream())
|
||||
}
|
117
crates/nu-command/src/commands/rotate/counter_clockwise.rs
Normal file
117
crates/nu-command/src/commands/rotate/counter_clockwise.rs
Normal file
|
@ -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<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[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<OutputStream, ShellError> {
|
||||
rotate(args).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn rotate(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
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<String> = 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::<Vec<_>>(),
|
||||
)
|
||||
.to_output_stream())
|
||||
}
|
5
crates/nu-command/src/commands/rotate/mod.rs
Normal file
5
crates/nu-command/src/commands/rotate/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod command;
|
||||
mod counter_clockwise;
|
||||
|
||||
pub use command::Command as Rotate;
|
||||
pub use counter_clockwise::SubCommand as RotateCounterClockwise;
|
|
@ -43,6 +43,7 @@ mod reduce;
|
|||
mod rename;
|
||||
mod reverse;
|
||||
mod rm;
|
||||
mod rotate;
|
||||
mod save;
|
||||
mod select;
|
||||
mod semicolon;
|
||||
|
|
83
crates/nu-command/tests/commands/rotate.rs
Normal file
83
crates/nu-command/tests/commands/rotate.rs
Normal file
|
@ -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);
|
||||
}
|
Loading…
Reference in a new issue