Port move (#833)

* Remove comment

* Fix merge not retaining LS_COLORS

* Add move command

* Add checking for non-existent columns

* Add move command examples; Disallow flag shorthand
This commit is contained in:
Jakub Žádník 2022-01-24 21:43:38 +02:00 committed by GitHub
parent 12189d417b
commit 53f41c1985
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 336 additions and 31 deletions

View File

@ -70,6 +70,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
GroupBy, GroupBy,
Keep, Keep,
Merge, Merge,
Move,
KeepUntil, KeepUntil,
KeepWhile, KeepWhile,
Last, Last,

View File

@ -72,6 +72,7 @@ impl Command for Merge {
let block: CaptureBlock = call.req(engine_state, stack, 0)?; let block: CaptureBlock = call.req(engine_state, stack, 0)?;
let mut stack = stack.captures_to_stack(&block.captures); let mut stack = stack.captures_to_stack(&block.captures);
let metadata = input.metadata();
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let block = engine_state.get_block(block.block_id); let block = engine_state.get_block(block.block_id);
let call = call.clone(); let call = call.clone();
@ -96,10 +97,12 @@ impl Command for Merge {
) => { ) => {
let mut table_iter = table.into_iter(); let mut table_iter = table.into_iter();
Ok(input let res =
input
.into_iter() .into_iter()
.map(move |inp| match (inp.as_record(), table_iter.next()) { .map(move |inp| match (inp.as_record(), table_iter.next()) {
(Ok((inp_cols, inp_vals)), Some(to_merge)) => match to_merge.as_record() { (Ok((inp_cols, inp_vals)), Some(to_merge)) => {
match to_merge.as_record() {
Ok((to_merge_cols, to_merge_vals)) => { Ok((to_merge_cols, to_merge_vals)) => {
let cols = [inp_cols, to_merge_cols].concat(); let cols = [inp_cols, to_merge_cols].concat();
let vals = [inp_vals, to_merge_vals].concat(); let vals = [inp_vals, to_merge_vals].concat();
@ -110,11 +113,17 @@ impl Command for Merge {
} }
} }
Err(error) => Value::Error { error }, Err(error) => Value::Error { error },
}, }
}
(_, None) => inp, (_, None) => inp,
(Err(error), _) => Value::Error { error }, (Err(error), _) => Value::Error { error },
}) });
.into_pipeline_data(ctrlc))
if let Some(md) = metadata {
Ok(res.into_pipeline_data_with_metadata(md, ctrlc))
} else {
Ok(res.into_pipeline_data(ctrlc))
}
} }
// record // record
( (
@ -170,20 +179,6 @@ impl Command for Merge {
} }
} }
/*
fn merge_values(
left: &UntaggedValue,
right: &UntaggedValue,
) -> Result<UntaggedValue, (&'static str, &'static str)> {
match (left, right) {
(UntaggedValue::Row(columns), UntaggedValue::Row(columns_b)) => {
Ok(UntaggedValue::Row(columns.merge_from(columns_b)))
}
(left, right) => Err((left.type_name(), right.type_name())),
}
}
*/
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View File

@ -18,6 +18,7 @@ mod last;
mod length; mod length;
mod lines; mod lines;
mod merge; mod merge;
mod move_;
mod nth; mod nth;
mod par_each; mod par_each;
mod prepend; mod prepend;
@ -56,6 +57,7 @@ pub use last::Last;
pub use length::Length; pub use length::Length;
pub use lines::Lines; pub use lines::Lines;
pub use merge::Merge; pub use merge::Merge;
pub use move_::Move;
pub use nth::Nth; pub use nth::Nth;
pub use par_each::ParEach; pub use par_each::ParEach;
pub use prepend::Prepend; pub use prepend::Prepend;

View File

@ -0,0 +1,302 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, Spanned, SyntaxShape, Value,
};
#[derive(Clone, Debug)]
enum BeforeOrAfter {
Before(String),
After(String),
}
#[derive(Clone)]
pub struct Move;
impl Command for Move {
fn name(&self) -> &str {
"move"
}
fn usage(&self) -> &str {
"Move columns before or after other columns"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("move")
.rest("columns", SyntaxShape::String, "the columns to move")
.named(
"after",
SyntaxShape::String,
"the column that will precede the columns moved",
None,
)
.named(
"before",
SyntaxShape::String,
"the column that will be the next after the columns moved",
None,
)
.category(Category::Filters)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "[[name value index]; [foo a 1] [bar b 2] [baz c 3]] | move index --before name",
description: "Move a column before the first column",
result:
Some(Value::List {
vals: vec![
Value::test_record(
vec!["index", "name", "value"],
vec![Value::test_int(1), Value::test_string("foo"), Value::test_string("a")],
),
Value::test_record(
vec!["index", "name", "value"],
vec![Value::test_int(2), Value::test_string("bar"), Value::test_string("b")],
),
Value::test_record(
vec!["index", "name", "value"],
vec![Value::test_int(3), Value::test_string("baz"), Value::test_string("c")],
),
],
span: Span::test_data(),
})
},
Example {
example: "[[name value index]; [foo a 1] [bar b 2] [baz c 3]] | move value name --after index",
description: "Move multiple columns after the last column and reorder them",
result:
Some(Value::List {
vals: vec![
Value::test_record(
vec!["index", "value", "name"],
vec![Value::test_int(1), Value::test_string("a"), Value::test_string("foo")],
),
Value::test_record(
vec!["index", "value", "name"],
vec![Value::test_int(2), Value::test_string("b"), Value::test_string("bar")],
),
Value::test_record(
vec!["index", "value", "name"],
vec![Value::test_int(3), Value::test_string("c"), Value::test_string("baz")],
),
],
span: Span::test_data(),
})
},
Example {
example: "{ name: foo, value: a, index: 1 } | move name --before index",
description: "Move columns of a record",
result: Some(Value::test_record(
vec!["value", "name", "index"],
vec![Value::test_string("a"), Value::test_string("foo"), Value::test_int(1)],
))
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let columns: Vec<Value> = call.rest(engine_state, stack, 0)?;
let after: Option<Value> = call.get_flag(engine_state, stack, "after")?;
let before: Option<Value> = call.get_flag(engine_state, stack, "before")?;
let before_or_after = match (after, before) {
(Some(v), None) => Spanned {
item: BeforeOrAfter::After(v.as_string()?),
span: v.span()?,
},
(None, Some(v)) => Spanned {
item: BeforeOrAfter::Before(v.as_string()?),
span: v.span()?,
},
(Some(_), Some(_)) => {
return Err(ShellError::SpannedLabeledError(
"Cannot move columns".to_string(),
"Use either --after, or --before, not both".to_string(),
call.head,
))
}
(None, None) => {
return Err(ShellError::SpannedLabeledError(
"Cannot move columns".to_string(),
"Missing --after or --before flag".to_string(),
call.head,
))
}
};
let metadata = input.metadata();
let ctrlc = engine_state.ctrlc.clone();
let call = call.clone();
match input {
PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream { .. } => {
let res = input.into_iter().map(move |x| match x.as_record() {
Ok((inp_cols, inp_vals)) => match move_record_columns(
inp_cols,
inp_vals,
&columns,
&before_or_after,
call.head,
) {
Ok(val) => val,
Err(error) => Value::Error { error },
},
Err(error) => Value::Error { error },
});
if let Some(md) = metadata {
Ok(res.into_pipeline_data_with_metadata(md, ctrlc))
} else {
Ok(res.into_pipeline_data(ctrlc))
}
}
PipelineData::Value(
Value::Record {
cols: inp_cols,
vals: inp_vals,
..
},
..,
) => Ok(move_record_columns(
&inp_cols,
&inp_vals,
&columns,
&before_or_after,
call.head,
)?
.into_pipeline_data()),
_ => Err(ShellError::PipelineMismatch(
"record or table".to_string(),
call.head,
Span::new(call.head.start, call.head.start),
)),
}
}
}
// Move columns within a record
fn move_record_columns(
inp_cols: &[String],
inp_vals: &[Value],
columns: &[Value],
before_or_after: &Spanned<BeforeOrAfter>,
span: Span,
) -> Result<Value, ShellError> {
let mut column_idx: Vec<usize> = Vec::with_capacity(columns.len());
// Check if before/after column exist
match &before_or_after.item {
BeforeOrAfter::After(after) => {
if !inp_cols.contains(after) {
return Err(ShellError::SpannedLabeledError(
"Cannot move columns".to_string(),
"column does not exist".to_string(),
before_or_after.span,
));
}
}
BeforeOrAfter::Before(before) => {
if !inp_cols.contains(before) {
return Err(ShellError::SpannedLabeledError(
"Cannot move columns".to_string(),
"column does not exist".to_string(),
before_or_after.span,
));
}
}
}
// Find indices of columns to be moved
for column in columns.iter() {
let column_str = column.as_string()?;
if let Some(idx) = inp_cols.iter().position(|inp_col| &column_str == inp_col) {
column_idx.push(idx);
} else {
return Err(ShellError::SpannedLabeledError(
"Cannot move columns".to_string(),
"column does not exist".to_string(),
column.span()?,
));
}
}
if columns.is_empty() {}
let mut out_cols: Vec<String> = Vec::with_capacity(inp_cols.len());
let mut out_vals: Vec<Value> = Vec::with_capacity(inp_vals.len());
for (i, (inp_col, inp_val)) in inp_cols.iter().zip(inp_vals).enumerate() {
match &before_or_after.item {
BeforeOrAfter::After(after) if after == inp_col => {
out_cols.push(inp_col.into());
out_vals.push(inp_val.clone());
for idx in column_idx.iter() {
if let (Some(col), Some(val)) = (inp_cols.get(*idx), inp_vals.get(*idx)) {
out_cols.push(col.into());
out_vals.push(val.clone());
} else {
return Err(ShellError::NushellFailedSpanned(
"Error indexing input columns".to_string(),
"originates from here".to_string(),
span,
));
}
}
}
BeforeOrAfter::Before(before) if before == inp_col => {
for idx in column_idx.iter() {
if let (Some(col), Some(val)) = (inp_cols.get(*idx), inp_vals.get(*idx)) {
out_cols.push(col.into());
out_vals.push(val.clone());
} else {
return Err(ShellError::NushellFailedSpanned(
"Error indexing input columns".to_string(),
"originates from here".to_string(),
span,
));
}
}
out_cols.push(inp_col.into());
out_vals.push(inp_val.clone());
}
_ => {
if !column_idx.contains(&i) {
out_cols.push(inp_col.into());
out_vals.push(inp_val.clone());
}
}
}
}
Ok(Value::Record {
cols: out_cols,
vals: out_vals,
span,
})
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Move {})
}
}

View File

@ -91,6 +91,11 @@ pub enum ShellError {
#[diagnostic(code(nu::shell::nushell_failed), url(docsrs))] #[diagnostic(code(nu::shell::nushell_failed), url(docsrs))]
NushellFailed(String), NushellFailed(String),
// Only use this one if we Nushell completely falls over and hits a state that isn't possible or isn't recoverable
#[error("Nushell failed: {0}.")]
#[diagnostic(code(nu::shell::nushell_failed), url(docsrs))]
NushellFailedSpanned(String, String, #[label = "{1}"] Span),
#[error("Variable not found")] #[error("Variable not found")]
#[diagnostic(code(nu::shell::variable_not_found), url(docsrs))] #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))]
VariableNotFoundAtRuntime(#[label = "variable not found"] Span), VariableNotFoundAtRuntime(#[label = "variable not found"] Span),