nu_command/filters/merge/
deep.rs1use super::common::{ListMerge, MergeStrategy, do_merge, typecheck_merge};
2use nu_engine::command_prelude::*;
3
4#[derive(Clone)]
5pub struct MergeDeep;
6
7impl Command for MergeDeep {
8 fn name(&self) -> &str {
9 "merge deep"
10 }
11
12 fn description(&self) -> &str {
13 "Merge the input with a record or table, recursively merging values in matching columns."
14 }
15
16 fn extra_description(&self) -> &str {
17 r#"The way that key-value pairs which exist in both the input and the argument are merged depends on their types.
18
19Scalar values (like numbers and strings) in the input are overwritten by the corresponding value from the argument.
20Records in the input are merged similarly to the merge command, but recursing rather than overwriting inner records.
21
22The way lists and tables are merged is controlled by the `--strategy` flag:
23 - table: Merges tables element-wise, similarly to the merge command. Non-table lists are overwritten.
24 - overwrite: Lists and tables are overwritten with their corresponding value from the argument, similarly to scalars.
25 - append: Lists and tables in the input are appended with the corresponding list from the argument.
26 - prepend: Lists and tables in the input are prepended with the corresponding list from the argument."#
27 }
28
29 fn signature(&self) -> nu_protocol::Signature {
30 Signature::build("merge deep")
31 .input_output_types(vec![
32 (Type::record(), Type::record()),
33 (Type::table(), Type::table()),
34 ])
35 .required(
36 "value",
37 SyntaxShape::OneOf(vec![
38 SyntaxShape::Record(vec![]),
39 SyntaxShape::Table(vec![]),
40 SyntaxShape::List(SyntaxShape::Any.into()),
41 ]),
42 "The new value to merge with.",
43 )
44 .category(Category::Filters)
45 .param(
46 Flag::new("strategy")
47 .short('s')
48 .arg(SyntaxShape::String)
49 .desc(
50 "The list merging strategy to use. One of: table (default), overwrite, \
51 append, prepend",
52 )
53 .completion(Completion::new_list(&[
54 "table",
55 "overwrite",
56 "append",
57 "prepend",
58 ])),
59 )
60 }
61
62 fn examples(&self) -> Vec<Example<'_>> {
63 vec![
64 Example {
65 example: "{a: 1, b: {c: 2, d: 3}} | merge deep {b: {d: 4, e: 5}}",
66 description: "Merge two records recursively",
67 result: Some(Value::test_record(record! {
68 "a" => Value::test_int(1),
69 "b" => Value::test_record(record! {
70 "c" => Value::test_int(2),
71 "d" => Value::test_int(4),
72 "e" => Value::test_int(5),
73 })
74 })),
75 },
76 Example {
77 example: r#"[{columnA: 0, columnB: [{B1: 1}]}] | merge deep [{columnB: [{B2: 2}]}]"#,
78 description: "Merge two tables",
79 result: Some(Value::test_list(vec![Value::test_record(record! {
80 "columnA" => Value::test_int(0),
81 "columnB" => Value::test_list(vec![
82 Value::test_record(record! {
83 "B1" => Value::test_int(1),
84 "B2" => Value::test_int(2),
85 })
86 ]),
87 })])),
88 },
89 Example {
90 example: r#"{inner: [{a: 1}, {b: 2}]} | merge deep {inner: [{c: 3}]}"#,
91 description: "Merge two records and their inner tables",
92 result: Some(Value::test_record(record! {
93 "inner" => Value::test_list(vec![
94 Value::test_record(record! {
95 "a" => Value::test_int(1),
96 "c" => Value::test_int(3),
97 }),
98 Value::test_record(record! {
99 "b" => Value::test_int(2),
100 })
101 ])
102 })),
103 },
104 Example {
105 example: r#"{inner: [{a: 1}, {b: 2}]} | merge deep {inner: [{c: 3}]} --strategy=append"#,
106 description: "Merge two records, appending their inner tables",
107 result: Some(Value::test_record(record! {
108 "inner" => Value::test_list(vec![
109 Value::test_record(record! {
110 "a" => Value::test_int(1),
111 }),
112 Value::test_record(record! {
113 "b" => Value::test_int(2),
114 }),
115 Value::test_record(record! {
116 "c" => Value::test_int(3),
117 }),
118 ])
119 })),
120 },
121 ]
122 }
123
124 fn run(
125 &self,
126 engine_state: &EngineState,
127 stack: &mut Stack,
128 call: &Call,
129 input: PipelineData,
130 ) -> Result<PipelineData, ShellError> {
131 let head = call.head;
132 let merge_value: Value = call.req(engine_state, stack, 0)?;
133 let strategy_flag: Option<String> = call.get_flag(engine_state, stack, "strategy")?;
134 let metadata = input.metadata();
135
136 let input_span = input.span().unwrap_or(head);
138 let input = input.into_value(input_span)?;
139
140 let strategy = match strategy_flag.as_deref() {
141 None | Some("table") => MergeStrategy::Deep(ListMerge::Elementwise),
142 Some("append") => MergeStrategy::Deep(ListMerge::Append),
143 Some("prepend") => MergeStrategy::Deep(ListMerge::Prepend),
144 Some("overwrite") => MergeStrategy::Deep(ListMerge::Overwrite),
145 Some(_) => {
146 return Err(ShellError::IncorrectValue {
147 msg: "The list merging strategy must be one one of: table, overwrite, append, prepend".to_string(),
148 val_span: call.get_flag_span(stack, "strategy").unwrap_or(head),
149 call_span: head,
150 })
151 }
152 };
153
154 typecheck_merge(&input, &merge_value, head)?;
155
156 let merged = do_merge(input, merge_value, strategy, head)?;
157 Ok(merged.into_pipeline_data_with_metadata(metadata))
158 }
159}
160
161#[cfg(test)]
162mod test {
163 use super::*;
164
165 #[test]
166 fn test_examples() {
167 use crate::test_examples;
168
169 test_examples(MergeDeep {})
170 }
171}