nu_command/filters/merge/
deep.rs

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