nu_command/filters/merge/
deep.rs

1use 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        // collect input before typechecking, so tables are detected as such
123        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}