Port merge command from Nushell (#808)

* Add example test to zip

* Port merge command from Nushell

On top of the original merge, this one should not collect a stream
returned from the merged block and allows merging records.
This commit is contained in:
Jakub Žádník 2022-01-22 01:50:26 +02:00 committed by GitHub
parent e1272f3b73
commit 564c2dd7d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 258 additions and 3 deletions

View File

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

View File

@ -14,7 +14,7 @@ use crate::To;
#[cfg(test)]
use super::{
Ansi, Date, From, If, Into, Math, Path, Random, Split, Str, StrCollect, StrFindReplace,
StrLength, Url,
StrLength, Url, Wrap,
};
#[cfg(test)]
@ -44,6 +44,7 @@ pub fn test_examples(cmd: impl Command + 'static) {
working_set.add_decl(Box::new(Date));
working_set.add_decl(Box::new(Url));
working_set.add_decl(Box::new(Ansi));
working_set.add_decl(Box::new(Wrap));
use super::Echo;
working_set.add_decl(Box::new(Echo));

View File

@ -0,0 +1,197 @@
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct Merge;
impl Command for Merge {
fn name(&self) -> &str {
"merge"
}
fn usage(&self) -> &str {
"Merge a table into an input table"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("merge")
.required(
"block",
SyntaxShape::Block(Some(vec![])),
"the block to run and merge into the table",
)
.category(Category::Filters)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "[a b c] | wrap name | merge { [1 2 3] | wrap index }",
description: "Merge an index column into the input table",
result: Some(Value::List {
vals: vec![
Value::test_record(
vec!["name", "index"],
vec![Value::test_string("a"), Value::test_int(1)],
),
Value::test_record(
vec!["name", "index"],
vec![Value::test_string("b"), Value::test_int(2)],
),
Value::test_record(
vec!["name", "index"],
vec![Value::test_string("c"), Value::test_int(3)],
),
],
span: Span::test_data(),
}),
},
Example {
example: "{a: 1, b: 2} | merge { {c: 3} }",
description: "Merge two records",
result: Some(Value::test_record(
vec!["a", "b", "c"],
vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
)),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
let mut stack = stack.captures_to_stack(&block.captures);
let ctrlc = engine_state.ctrlc.clone();
let block = engine_state.get_block(block.block_id);
let call = call.clone();
let result = eval_block(
engine_state,
&mut stack,
block,
PipelineData::new(call.head),
);
let table = match result {
Ok(res) => res,
Err(e) => return Err(e),
};
match (&input, &table) {
// table (list of records)
(
PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream { .. },
PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream { .. },
) => {
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,
}
}
Err(error) => Value::Error { error },
},
(_, None) => inp,
(Err(error), _) => Value::Error { error },
})
.into_pipeline_data(ctrlc))
}
// record
(
PipelineData::Value(
Value::Record {
cols: inp_cols,
vals: inp_vals,
..
},
..,
),
PipelineData::Value(
Value::Record {
cols: to_merge_cols,
vals: to_merge_vals,
..
},
..,
),
) => {
let mut cols = inp_cols.to_vec();
cols.extend(to_merge_cols.to_vec());
let mut vals = inp_vals.to_vec();
vals.extend(to_merge_vals.to_vec());
Ok(Value::Record {
cols,
vals,
span: call.head,
}
.into_pipeline_data())
}
(_, PipelineData::Value(val, ..)) | (PipelineData::Value(val, ..), _) => {
let span = if val.span()? == Span::test_data() {
Span::new(call.head.start, call.head.start)
} else {
val.span()?
};
Err(ShellError::PipelineMismatch(
"record or table in both the input and the argument block".to_string(),
call.head,
span,
))
}
_ => Err(ShellError::PipelineMismatch(
"record or table in both the input and the argument block".to_string(),
call.head,
Span::new(call.head.start, call.head.start),
)),
}
}
}
/*
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::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Merge {})
}
}

View File

@ -16,6 +16,7 @@ mod keep;
mod last;
mod length;
mod lines;
mod merge;
mod nth;
mod par_each;
mod prepend;
@ -51,6 +52,7 @@ pub use keep::*;
pub use last::Last;
pub use length::Length;
pub use lines::Lines;
pub use merge::Merge;
pub use nth::Nth;
pub use par_each::ParEach;
pub use prepend::Prepend;

View File

@ -3,7 +3,7 @@ use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature,
SyntaxShape, Value,
Span, SyntaxShape, Value,
};
#[derive(Clone)]
@ -25,10 +25,55 @@ impl Command for Zip {
}
fn examples(&self) -> Vec<Example> {
let test_row_1 = Value::List {
vals: vec![
Value::Int {
val: 1,
span: Span::test_data(),
},
Value::Int {
val: 4,
span: Span::test_data(),
},
],
span: Span::test_data(),
};
let test_row_2 = Value::List {
vals: vec![
Value::Int {
val: 2,
span: Span::test_data(),
},
Value::Int {
val: 5,
span: Span::test_data(),
},
],
span: Span::test_data(),
};
let test_row_3 = Value::List {
vals: vec![
Value::Int {
val: 3,
span: Span::test_data(),
},
Value::Int {
val: 6,
span: Span::test_data(),
},
],
span: Span::test_data(),
};
vec![Example {
example: "1..3 | zip 4..6",
description: "Zip multiple streams and get one of the results",
result: None,
result: Some(Value::List {
vals: vec![test_row_1, test_row_2, test_row_3],
span: Span::test_data(),
}),
}]
}

View File

@ -797,6 +797,15 @@ impl Value {
span: Span::test_data(),
}
}
// Only use these for test data. Should not be used in user data
pub fn test_record(cols: Vec<impl Into<String>>, vals: Vec<Value>) -> Value {
Value::Record {
cols: cols.into_iter().map(|s| s.into()).collect(),
vals,
span: Span::test_data(),
}
}
}
impl Default for Value {