forked from extern/nushell
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
@ -70,6 +70,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
||||
GroupBy,
|
||||
Keep,
|
||||
Merge,
|
||||
Move,
|
||||
KeepUntil,
|
||||
KeepWhile,
|
||||
Last,
|
||||
|
@ -72,6 +72,7 @@ impl Command for Merge {
|
||||
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
|
||||
let mut stack = stack.captures_to_stack(&block.captures);
|
||||
|
||||
let metadata = input.metadata();
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let block = engine_state.get_block(block.block_id);
|
||||
let call = call.clone();
|
||||
@ -96,25 +97,33 @@ impl Command for Merge {
|
||||
) => {
|
||||
let mut table_iter = table.into_iter();
|
||||
|
||||
Ok(input
|
||||
.into_iter()
|
||||
.map(move |inp| match (inp.as_record(), table_iter.next()) {
|
||||
(Ok((inp_cols, inp_vals)), Some(to_merge)) => match to_merge.as_record() {
|
||||
Ok((to_merge_cols, to_merge_vals)) => {
|
||||
let cols = [inp_cols, to_merge_cols].concat();
|
||||
let vals = [inp_vals, to_merge_vals].concat();
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: call.head,
|
||||
let res =
|
||||
input
|
||||
.into_iter()
|
||||
.map(move |inp| match (inp.as_record(), table_iter.next()) {
|
||||
(Ok((inp_cols, inp_vals)), Some(to_merge)) => {
|
||||
match to_merge.as_record() {
|
||||
Ok((to_merge_cols, to_merge_vals)) => {
|
||||
let cols = [inp_cols, to_merge_cols].concat();
|
||||
let vals = [inp_vals, to_merge_vals].concat();
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: call.head,
|
||||
}
|
||||
}
|
||||
Err(error) => Value::Error { error },
|
||||
}
|
||||
}
|
||||
Err(error) => Value::Error { error },
|
||||
},
|
||||
(_, None) => inp,
|
||||
(Err(error), _) => Value::Error { error },
|
||||
})
|
||||
.into_pipeline_data(ctrlc))
|
||||
(_, None) => inp,
|
||||
(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))
|
||||
}
|
||||
}
|
||||
// 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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@ -18,6 +18,7 @@ mod last;
|
||||
mod length;
|
||||
mod lines;
|
||||
mod merge;
|
||||
mod move_;
|
||||
mod nth;
|
||||
mod par_each;
|
||||
mod prepend;
|
||||
@ -56,6 +57,7 @@ pub use last::Last;
|
||||
pub use length::Length;
|
||||
pub use lines::Lines;
|
||||
pub use merge::Merge;
|
||||
pub use move_::Move;
|
||||
pub use nth::Nth;
|
||||
pub use par_each::ParEach;
|
||||
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))]
|
||||
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")]
|
||||
#[diagnostic(code(nu::shell::variable_not_found), url(docsrs))]
|
||||
VariableNotFoundAtRuntime(#[label = "variable not found"] Span),
|
||||
|
Loading…
Reference in New Issue
Block a user