From c731a5b628d7e7e005f3acc18c778b5b5e1bced7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Tue, 3 Mar 2020 16:01:24 -0500 Subject: [PATCH] Columns can be renamed. (#1447) --- crates/nu-protocol/src/value/dict.rs | 5 ++ src/cli.rs | 1 + src/commands.rs | 2 + src/commands/rename.rs | 97 ++++++++++++++++++++++++++++ tests/commands/mod.rs | 1 + tests/commands/rename.rs | 97 ++++++++++++++++++++++++++++ 6 files changed, 203 insertions(+) create mode 100644 src/commands/rename.rs create mode 100644 tests/commands/rename.rs diff --git a/crates/nu-protocol/src/value/dict.rs b/crates/nu-protocol/src/value/dict.rs index 780dfd4d1..418192330 100644 --- a/crates/nu-protocol/src/value/dict.rs +++ b/crates/nu-protocol/src/value/dict.rs @@ -130,6 +130,11 @@ impl Dictionary { self.entries.keys() } + /// Iterate the values in the Dictionary + pub fn values(&self) -> impl Iterator { + self.entries.values() + } + /// Checks if given key exists pub fn contains_key(&self, key: &str) -> bool { self.entries.contains_key(key) diff --git a/src/cli.rs b/src/cli.rs index 20199d475..d28f94c3f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -318,6 +318,7 @@ pub fn create_default_context( whole_stream_command(Default), whole_stream_command(SkipWhile), whole_stream_command(Range), + whole_stream_command(Rename), whole_stream_command(Uniq), // Table manipulation whole_stream_command(Wrap), diff --git a/src/commands.rs b/src/commands.rs index f96178ef7..3dfb3d17c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -69,6 +69,7 @@ pub(crate) mod range; #[allow(unused)] pub(crate) mod reduce_by; pub(crate) mod reject; +pub(crate) mod rename; pub(crate) mod reverse; pub(crate) mod rm; pub(crate) mod save; @@ -172,6 +173,7 @@ pub(crate) use range::Range; #[allow(unused_imports)] pub(crate) use reduce_by::ReduceBy; pub(crate) use reject::Reject; +pub(crate) use rename::Rename; pub(crate) use reverse::Reverse; pub(crate) use rm::Remove; pub(crate) use save::Save; diff --git a/src/commands/rename.rs b/src/commands/rename.rs new file mode 100644 index 000000000..1b45bedfd --- /dev/null +++ b/src/commands/rename.rs @@ -0,0 +1,97 @@ +use crate::commands::WholeStreamCommand; +use crate::prelude::*; +use indexmap::IndexMap; +use nu_errors::ShellError; +use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; +use nu_source::Tagged; + +pub struct Rename; + +#[derive(Deserialize)] +pub struct Arguments { + column_name: Tagged, + rest: Vec>, +} + +impl WholeStreamCommand for Rename { + fn name(&self) -> &str { + "rename" + } + + fn signature(&self) -> Signature { + Signature::build("rename") + .required( + "column_name", + SyntaxShape::String, + "the name of the column to rename for", + ) + .rest( + SyntaxShape::Member, + "Additional column name(s) to rename for", + ) + } + + fn usage(&self) -> &str { + "Creates a new table with columns renamed." + } + + fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + args.process(registry, rename)?.run() + } +} + +pub fn rename( + Arguments { column_name, rest }: Arguments, + RunnableContext { input, name, .. }: RunnableContext, +) -> Result { + let mut new_column_names = vec![vec![column_name]]; + new_column_names.push(rest); + + let new_column_names = new_column_names.into_iter().flatten().collect::>(); + + let stream = input + .values + .map(move |item| { + let mut result = VecDeque::new(); + + if let Value { + value: UntaggedValue::Row(row), + tag, + } = item + { + let mut renamed_row = IndexMap::new(); + + for (idx, (key, value)) in row.entries.iter().enumerate() { + let key = if idx < new_column_names.len() { + &new_column_names[idx].item + } else { + key + }; + + renamed_row.insert(key.clone(), value.clone()); + } + + let out = UntaggedValue::Row(renamed_row.into()).into_value(tag); + + result.push_back(ReturnSuccess::value(out)); + } else { + result.push_back(ReturnSuccess::value( + UntaggedValue::Error(ShellError::labeled_error( + "no column names available", + "can't rename", + &name, + )) + .into_untagged_value(), + )); + } + + futures::stream::iter(result) + }) + .flatten(); + + Ok(stream.to_output_stream()) +} diff --git a/tests/commands/mod.rs b/tests/commands/mod.rs index 7ed188813..197c10af6 100644 --- a/tests/commands/mod.rs +++ b/tests/commands/mod.rs @@ -22,6 +22,7 @@ mod parse; mod pick; mod prepend; mod range; +mod rename; mod reverse; mod rm; mod save; diff --git a/tests/commands/rename.rs b/tests/commands/rename.rs new file mode 100644 index 000000000..00cf71416 --- /dev/null +++ b/tests/commands/rename.rs @@ -0,0 +1,97 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, nu_error, pipeline}; + +#[test] +fn changes_the_column_name() { + Playground::setup("rename_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_cuatro_mosqueteros.txt", + r#" + Andrés N. Robalino + Jonathan Turner + Yehuda Katz + Jason Gedge + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_cuatro_mosqueteros.txt + | lines + | wrap name + | rename mosqueteros + | get mosqueteros + | count + | echo $it + "# + )); + + assert_eq!(actual, "4"); + }) +} + +#[test] +fn keeps_remaining_original_names_given_less_new_names_than_total_original_names() { + Playground::setup("rename_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_cuatro_mosqueteros.txt", + r#" + Andrés N. Robalino + Jonathan Turner + Yehuda Katz + Jason Gedge + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_cuatro_mosqueteros.txt + | lines + | wrap name + | default hit "arepa!" + | rename mosqueteros + | get hit + | count + | echo $it + "# + )); + + assert_eq!(actual, "4"); + }) +} + +#[test] +fn errors_if_no_columns_present() { + Playground::setup("rename_test_3", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_cuatro_mosqueteros.txt", + r#" + Andrés N. Robalino + Jonathan Turner + Yehuda Katz + Jason Gedge + "#, + )]); + + let actual = nu_error!( + cwd: dirs.test(), pipeline( + r#" + open los_cuatro_mosqueteros.txt + | lines + | rename mosqueteros + "# + )); + + assert!( + actual.contains("no column names available"), + format!("actual: {:?}", actual) + ); + assert!( + actual.contains("can't rename"), + format!("actual: {:?}", actual) + ); + }) +}