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 metadata = input.metadata(); 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), call.redirect_stdout, call.redirect_stderr, ); 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(); 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 }, } } (_, 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 ( 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), )), } } } #[cfg(test)] mod test { use super::*; #[test] fn test_examples() { use crate::test_examples; test_examples(Merge {}) } }