nu_command/filters/merge/
deep.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use super::common::{do_merge, typecheck_merge, ListMerge, MergeStrategy};
use nu_engine::command_prelude::*;

#[derive(Clone)]
pub struct MergeDeep;

impl Command for MergeDeep {
    fn name(&self) -> &str {
        "merge deep"
    }

    fn description(&self) -> &str {
        "Merge the input with a record or table, recursively merging values in matching columns."
    }

    fn extra_description(&self) -> &str {
        r#"The way that key-value pairs which exist in both the input and the argument are merged depends on their types.

Scalar values (like numbers and strings) in the input are overwritten by the corresponding value from the argument.
Records in the input are merged similarly to the merge command, but recursing rather than overwriting inner records.

The way lists and tables are merged is controlled by the `--strategy` flag:
  - table: Merges tables element-wise, similarly to the merge command. Non-table lists are overwritten.
  - overwrite: Lists and tables are overwritten with their corresponding value from the argument, similarly to scalars.
  - append: Lists and tables in the input are appended with the corresponding list from the argument.
  - prepend: Lists and tables in the input are prepended with the corresponding list from the argument."#
    }

    fn signature(&self) -> nu_protocol::Signature {
        Signature::build("merge deep")
            .input_output_types(vec![
                (Type::record(), Type::record()),
                (Type::table(), Type::table()),
            ])
            .required(
                "value",
                SyntaxShape::OneOf(vec![
                    SyntaxShape::Record(vec![]),
                    SyntaxShape::Table(vec![]),
                    SyntaxShape::List(SyntaxShape::Any.into()),
                ]),
                "The new value to merge with.",
            )
            .category(Category::Filters)
            .named("strategy", SyntaxShape::String, "The list merging strategy to use. One of: table (default), overwrite, append, prepend", Some('s'))
    }

    fn examples(&self) -> Vec<Example> {
        vec![
            Example {
                example: "{a: 1, b: {c: 2, d: 3}} | merge deep {b: {d: 4, e: 5}}",
                description: "Merge two records recursively",
                result: Some(Value::test_record(record! {
                    "a" => Value::test_int(1),
                    "b" => Value::test_record(record! {
                        "c" => Value::test_int(2),
                        "d" => Value::test_int(4),
                        "e" => Value::test_int(5),
                    })
                })),
            },
            Example {
                example: r#"[{columnA: 0, columnB: [{B1: 1}]}] | merge deep [{columnB: [{B2: 2}]}]"#,
                description: "Merge two tables",
                result: Some(Value::test_list(vec![Value::test_record(record! {
                    "columnA" => Value::test_int(0),
                    "columnB" => Value::test_list(vec![
                        Value::test_record(record! {
                            "B1" => Value::test_int(1),
                            "B2" => Value::test_int(2),
                        })
                    ]),
                })])),
            },
            Example {
                example: r#"{inner: [{a: 1}, {b: 2}]} | merge deep {inner: [{c: 3}]}"#,
                description: "Merge two records and their inner tables",
                result: Some(Value::test_record(record! {
                    "inner" => Value::test_list(vec![
                        Value::test_record(record! {
                            "a" => Value::test_int(1),
                            "c" => Value::test_int(3),
                        }),
                        Value::test_record(record! {
                            "b" => Value::test_int(2),
                        })
                    ])
                })),
            },
            Example {
                example: r#"{inner: [{a: 1}, {b: 2}]} | merge deep {inner: [{c: 3}]} --strategy=append"#,
                description: "Merge two records, appending their inner tables",
                result: Some(Value::test_record(record! {
                    "inner" => Value::test_list(vec![
                        Value::test_record(record! {
                            "a" => Value::test_int(1),
                        }),
                        Value::test_record(record! {
                            "b" => Value::test_int(2),
                        }),
                        Value::test_record(record! {
                            "c" => Value::test_int(3),
                        }),
                    ])
                })),
            },
        ]
    }

    fn run(
        &self,
        engine_state: &EngineState,
        stack: &mut Stack,
        call: &Call,
        input: PipelineData,
    ) -> Result<PipelineData, ShellError> {
        let head = call.head;
        let merge_value: Value = call.req(engine_state, stack, 0)?;
        let strategy_flag: Option<String> = call.get_flag(engine_state, stack, "strategy")?;
        let metadata = input.metadata();

        // collect input before typechecking, so tables are detected as such
        let input_span = input.span().unwrap_or(head);
        let input = input.into_value(input_span)?;

        let strategy = match strategy_flag.as_deref() {
            None | Some("table") => MergeStrategy::Deep(ListMerge::Elementwise),
            Some("append") => MergeStrategy::Deep(ListMerge::Append),
            Some("prepend") => MergeStrategy::Deep(ListMerge::Prepend),
            Some("overwrite") => MergeStrategy::Deep(ListMerge::Overwrite),
            Some(_) => {
                return Err(ShellError::IncorrectValue {
                    msg: "The list merging strategy must be one one of: table, overwrite, append, prepend".to_string(),
                    val_span: call.get_flag_span(stack, "strategy").unwrap_or(head),
                    call_span: head,
                })
            }
        };

        typecheck_merge(&input, &merge_value, head)?;

        let merged = do_merge(input, merge_value, strategy, head)?;
        Ok(merged.into_pipeline_data_with_metadata(metadata))
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_examples() {
        use crate::test_examples;

        test_examples(MergeDeep {})
    }
}