nu_command/filters/merge/
deep.rs1use super::common::{do_merge, typecheck_merge, ListMerge, MergeStrategy};
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 .named("strategy", SyntaxShape::String, "The list merging strategy to use. One of: table (default), overwrite, append, prepend", Some('s'))
46 }
47
48 fn examples(&self) -> Vec<Example> {
49 vec![
50 Example {
51 example: "{a: 1, b: {c: 2, d: 3}} | merge deep {b: {d: 4, e: 5}}",
52 description: "Merge two records recursively",
53 result: Some(Value::test_record(record! {
54 "a" => Value::test_int(1),
55 "b" => Value::test_record(record! {
56 "c" => Value::test_int(2),
57 "d" => Value::test_int(4),
58 "e" => Value::test_int(5),
59 })
60 })),
61 },
62 Example {
63 example: r#"[{columnA: 0, columnB: [{B1: 1}]}] | merge deep [{columnB: [{B2: 2}]}]"#,
64 description: "Merge two tables",
65 result: Some(Value::test_list(vec![Value::test_record(record! {
66 "columnA" => Value::test_int(0),
67 "columnB" => Value::test_list(vec![
68 Value::test_record(record! {
69 "B1" => Value::test_int(1),
70 "B2" => Value::test_int(2),
71 })
72 ]),
73 })])),
74 },
75 Example {
76 example: r#"{inner: [{a: 1}, {b: 2}]} | merge deep {inner: [{c: 3}]}"#,
77 description: "Merge two records and their inner tables",
78 result: Some(Value::test_record(record! {
79 "inner" => Value::test_list(vec![
80 Value::test_record(record! {
81 "a" => Value::test_int(1),
82 "c" => Value::test_int(3),
83 }),
84 Value::test_record(record! {
85 "b" => Value::test_int(2),
86 })
87 ])
88 })),
89 },
90 Example {
91 example: r#"{inner: [{a: 1}, {b: 2}]} | merge deep {inner: [{c: 3}]} --strategy=append"#,
92 description: "Merge two records, appending their inner tables",
93 result: Some(Value::test_record(record! {
94 "inner" => Value::test_list(vec![
95 Value::test_record(record! {
96 "a" => Value::test_int(1),
97 }),
98 Value::test_record(record! {
99 "b" => Value::test_int(2),
100 }),
101 Value::test_record(record! {
102 "c" => Value::test_int(3),
103 }),
104 ])
105 })),
106 },
107 ]
108 }
109
110 fn run(
111 &self,
112 engine_state: &EngineState,
113 stack: &mut Stack,
114 call: &Call,
115 input: PipelineData,
116 ) -> Result<PipelineData, ShellError> {
117 let head = call.head;
118 let merge_value: Value = call.req(engine_state, stack, 0)?;
119 let strategy_flag: Option<String> = call.get_flag(engine_state, stack, "strategy")?;
120 let metadata = input.metadata();
121
122 let input_span = input.span().unwrap_or(head);
124 let input = input.into_value(input_span)?;
125
126 let strategy = match strategy_flag.as_deref() {
127 None | Some("table") => MergeStrategy::Deep(ListMerge::Elementwise),
128 Some("append") => MergeStrategy::Deep(ListMerge::Append),
129 Some("prepend") => MergeStrategy::Deep(ListMerge::Prepend),
130 Some("overwrite") => MergeStrategy::Deep(ListMerge::Overwrite),
131 Some(_) => {
132 return Err(ShellError::IncorrectValue {
133 msg: "The list merging strategy must be one one of: table, overwrite, append, prepend".to_string(),
134 val_span: call.get_flag_span(stack, "strategy").unwrap_or(head),
135 call_span: head,
136 })
137 }
138 };
139
140 typecheck_merge(&input, &merge_value, head)?;
141
142 let merged = do_merge(input, merge_value, strategy, head)?;
143 Ok(merged.into_pipeline_data_with_metadata(metadata))
144 }
145}
146
147#[cfg(test)]
148mod test {
149 use super::*;
150
151 #[test]
152 fn test_examples() {
153 use crate::test_examples;
154
155 test_examples(MergeDeep {})
156 }
157}