From 564c2dd7d15f01d3e5fe95baf56d35e22baba47e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sat, 22 Jan 2022 01:50:26 +0200 Subject: [PATCH] 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. --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/example_test.rs | 3 +- crates/nu-command/src/filters/merge.rs | 197 +++++++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/src/filters/zip_.rs | 49 +++++- crates/nu-protocol/src/value/mod.rs | 9 ++ 6 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 crates/nu-command/src/filters/merge.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index c2dce24a15..4e9716ee28 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -67,6 +67,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Get, GroupBy, Keep, + Merge, KeepUntil, KeepWhile, Last, diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index e7ddefb4c4..c3bd6fd514 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -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)); diff --git a/crates/nu-command/src/filters/merge.rs b/crates/nu-command/src/filters/merge.rs new file mode 100644 index 0000000000..51db1f193a --- /dev/null +++ b/crates/nu-command/src/filters/merge.rs @@ -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 { + 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 { + 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 { +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 {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 6a84108355..0eb48165bc 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -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; diff --git a/crates/nu-command/src/filters/zip_.rs b/crates/nu-command/src/filters/zip_.rs index a0f273ebfe..6f36288ca4 100644 --- a/crates/nu-command/src/filters/zip_.rs +++ b/crates/nu-command/src/filters/zip_.rs @@ -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 { + 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(), + }), }] } diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index c050ea0186..c0e7e6db58 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -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>, vals: Vec) -> Value { + Value::Record { + cols: cols.into_iter().map(|s| s.into()).collect(), + vals, + span: Span::test_data(), + } + } } impl Default for Value {