Rename: change the SyntaxShape of -c flag from list to record (#10526)

# Description
Fixes: #7085 
Also closes: #7526 

# User-Facing Changes
After this change, we need to use `-c` flag like this:
```nushell
[[a, b, c]; [1, 2, 3]] | rename -c { a: ham }
```
But we can rename many columns easily, here is another example:
```nushell
[[a, b, c]; [1, 2, 3]] | rename -c { a: ham, b: ham2 }
```
This commit is contained in:
WindSoilder 2023-09-30 21:59:47 +08:00 committed by GitHub
parent 85d6529f0d
commit d34581db4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 44 additions and 38 deletions

View file

@ -1,3 +1,4 @@
use indexmap::IndexMap;
use nu_engine::{eval_block_with_early_return, CallExt}; use nu_engine::{eval_block_with_early_return, CallExt};
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack}; use nu_protocol::engine::{Closure, Command, EngineState, Stack};
@ -5,6 +6,7 @@ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span, Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
SyntaxShape, Type, Value, SyntaxShape, Type, Value,
}; };
use std::collections::HashSet;
#[derive(Clone)] #[derive(Clone)]
pub struct Rename; pub struct Rename;
@ -22,7 +24,7 @@ impl Command for Rename {
]) ])
.named( .named(
"column", "column",
SyntaxShape::List(Box::new(SyntaxShape::String)), SyntaxShape::Record(vec![]),
"column name to be changed", "column name to be changed",
Some('c'), Some('c'),
) )
@ -76,7 +78,7 @@ impl Command for Rename {
}, },
Example { Example {
description: "Rename a specific column", description: "Rename a specific column",
example: "[[a, b, c]; [1, 2, 3]] | rename -c [a ham]", example: "[[a, b, c]; [1, 2, 3]] | rename -c { a: ham }",
result: Some(Value::list( result: Some(Value::list(
vec![Value::test_record(Record { vec![Value::test_record(Record {
cols: vec!["ham".to_string(), "b".to_string(), "c".to_string()], cols: vec!["ham".to_string(), "b".to_string(), "c".to_string()],
@ -111,34 +113,35 @@ fn rename(
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let specified_column: Option<Vec<String>> = call.get_flag(engine_state, stack, "column")?; let specified_column: Option<Record> = call.get_flag(engine_state, stack, "column")?;
// get the span for the column's name to be changed and for the given list // convert from Record to HashMap for easily query.
let column_flag: Option<Value> = call.get_flag(engine_state, stack, "column")?; let specified_column: Option<IndexMap<String, String>> = match specified_column {
let (specified_col_span, list_span) = match column_flag { Some(query) => {
Some(column_flag) => { let mut columns = IndexMap::new();
let column_span = column_flag.span(); for (col, val) in query {
match column_flag { let val_span = val.span();
Value::List { vals: columns, .. } => { match val {
if columns.is_empty() { Value::String { val, .. } => {
return Err(ShellError::TypeMismatch { err_message: "The column list cannot be empty and must contain only two values: the column's name and its replacement value" columns.insert(col, val);
.to_string(), span: column_span }); }
} else { _ => {
(Some(columns[0].span()), column_span) return Err(ShellError::TypeMismatch {
err_message: "new column name must be a string".to_owned(),
span: val_span,
});
} }
} }
_ => (None, call.head),
} }
if columns.is_empty() {
return Err(ShellError::TypeMismatch {
err_message: "The column info cannot be empty".to_owned(),
span: call.head,
});
}
Some(columns)
} }
None => (None, call.head), None => None,
}; };
if let Some(ref cols) = specified_column {
if cols.len() != 2 {
return Err(ShellError::TypeMismatch { err_message: "The column list must contain only two values: the column's name and its replacement value"
.to_string(), span: list_span });
}
}
let redirect_stdout = call.redirect_stdout; let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr; let redirect_stderr = call.redirect_stderr;
let block_info = let block_info =
@ -195,29 +198,32 @@ fn rename(
} else { } else {
match &specified_column { match &specified_column {
Some(c) => { Some(c) => {
// check if the specified column to be renamed exists let mut column_to_rename: HashSet<String> = HashSet::from_iter(c.keys().cloned());
if !record.cols.contains(&c[0]) { for val in record.cols.iter_mut() {
if c.contains_key(val) {
column_to_rename.remove(val);
*val = c.get(val).expect("already check exists").to_owned();
}
}
if !column_to_rename.is_empty() {
let not_exists_column =
column_to_rename.into_iter().next().expect(
"already checked column to rename still exists",
);
return Value::error( return Value::error(
ShellError::UnsupportedInput( ShellError::UnsupportedInput(
format!( format!(
"The column '{}' does not exist in the input", "The column '{not_exists_column}' does not exist in the input",
&c[0]
), ),
"value originated from here".into(), "value originated from here".into(),
// Arrow 1 points at the specified column name, // Arrow 1 points at the specified column name,
specified_col_span.unwrap_or(head_span), head_span,
// Arrow 2 points at the input value. // Arrow 2 points at the input value.
span, span,
), ),
span, span,
); );
} }
for (idx, val) in record.cols.iter_mut().enumerate() {
if *val == c[0] {
record.cols[idx] = c[1].to_string();
break;
}
}
} }
None => { None => {
for (idx, val) in columns.iter().enumerate() { for (idx, val) in columns.iter().enumerate() {

View file

@ -107,10 +107,10 @@ fn errors_if_columns_param_is_empty() {
| lines | lines
| wrap name | wrap name
| default "arepa!" hit | default "arepa!" hit
| rename -c [] | rename -c {}
"# "#
)); ));
assert!(actual.err.contains("The column list cannot be empty")); assert!(actual.err.contains("The column info cannot be empty"));
}) })
} }