mirror of
https://github.com/nushell/nushell
synced 2024-12-28 14:03:09 +00:00
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:
parent
12189d417b
commit
53f41c1985
5 changed files with 336 additions and 31 deletions
|
@ -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,
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
302
crates/nu-command/src/filters/move_.rs
Normal file
302
crates/nu-command/src/filters/move_.rs
Normal 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 {})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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),
|
||||||
|
|
Loading…
Reference in a new issue