diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 166146aec..a58ed69c7 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -83,6 +83,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Range, Reduce, Reject, + Rename, Reverse, Select, Shuffle, diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 3340917e4..4b5e73bd9 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -26,6 +26,7 @@ mod prepend; mod range; mod reduce; mod reject; +mod rename; mod reverse; mod select; mod shuffle; @@ -66,6 +67,7 @@ pub use prepend::Prepend; pub use range::Range; pub use reduce::Reduce; pub use reject::Reject; +pub use rename::Rename; pub use reverse::Reverse; pub use select::Select; pub use shuffle::Shuffle; diff --git a/crates/nu-command/src/filters/rename.rs b/crates/nu-command/src/filters/rename.rs new file mode 100644 index 000000000..385b2064d --- /dev/null +++ b/crates/nu-command/src/filters/rename.rs @@ -0,0 +1,168 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Rename; + +impl Command for Rename { + fn name(&self) -> &str { + "rename" + } + + fn signature(&self) -> Signature { + Signature::build("rename") + .named( + "column", + SyntaxShape::List(Box::new(SyntaxShape::String)), + "column name to be changed", + Some('c'), + ) + .rest("rest", SyntaxShape::String, "the new names for the columns") + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Creates a new table with columns renamed." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + rename(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Rename a column", + example: "[[a, b]; [1, 2]] | rename my_column", + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec!["my_column".to_string(), "b".to_string()], + vals: vec![Value::test_int(1), Value::test_int(2)], + span: Span::test_data(), + }], + span: Span::test_data(), + }), + }, + Example { + description: "Rename many columns", + example: "[[a, b, c]; [1, 2, 3]] | rename eggs ham bacon", + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec!["eggs".to_string(), "ham".to_string(), "bacon".to_string()], + vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)], + span: Span::test_data(), + }], + span: Span::test_data(), + }), + }, + Example { + description: "Rename a specific column", + example: "[[a, b, c]; [1, 2, 3]] | rename -c [a ham]", + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec!["ham".to_string(), "b".to_string(), "c".to_string()], + vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)], + span: Span::test_data(), + }], + span: Span::test_data(), + }), + }, + ] + } +} + +fn rename( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let specified_column: Option> = call.get_flag(engine_state, stack, "column")?; + // get the span for the column's name to be changed and for the given list + let (specified_col_span, list_span) = if let Some(Value::List { + vals: columns, + span: column_span, + }) = call.get_flag(engine_state, stack, "column")? + { + (Some(columns[0].span()?), column_span) + } else { + (None, call.head) + }; + + if let Some(ref cols) = specified_column { + if cols.len() != 2 { + return Err(ShellError::UnsupportedInput( + "The list must contain only two values: the column's name and its replacement value" + .to_string(), + list_span, + )); + } + } + + let columns: Vec = call.rest(engine_state, stack, 0)?; + + input.map( + move |item| match item { + Value::Record { + mut cols, + vals, + span, + } => { + match &specified_column { + Some(c) => { + // check if the specified column to be renamed exists + if !cols.contains(&c[0]) { + return Value::Error { + error: ShellError::UnsupportedInput( + "The specified column does not exist".to_string(), + specified_col_span.unwrap_or(span), + ), + }; + } + for (idx, val) in cols.iter_mut().enumerate() { + if *val == c[0] { + cols[idx] = c[1].to_string(); + break; + } + } + } + None => { + for (idx, val) in columns.iter().enumerate() { + if idx > cols.len() - 1 { + // skip extra new columns names if we already reached the final column + break; + } + cols[idx] = val.clone(); + } + } + } + + Value::Record { cols, vals, span } + } + x => x, + }, + engine_state.ctrlc.clone(), + ) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Rename {}) + } +}