nu_command/filters/
default.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::{ListStream, Signals};
3
4#[derive(Clone)]
5pub struct Default;
6
7impl Command for Default {
8    fn name(&self) -> &str {
9        "default"
10    }
11
12    fn signature(&self) -> Signature {
13        Signature::build("default")
14            // TODO: Give more specific type signature?
15            // TODO: Declare usage of cell paths in signature? (It seems to behave as if it uses cell paths)
16            .input_output_types(vec![(Type::Any, Type::Any)])
17            .required(
18                "default value",
19                SyntaxShape::Any,
20                "The value to use as a default.",
21            )
22            .optional(
23                "column name",
24                SyntaxShape::String,
25                "The name of the column.",
26            )
27            .switch(
28                "empty",
29                "also replace empty items like \"\", {}, and []",
30                Some('e'),
31            )
32            .category(Category::Filters)
33    }
34
35    fn description(&self) -> &str {
36        "Sets a default value if a row's column is missing or null."
37    }
38
39    fn run(
40        &self,
41        engine_state: &EngineState,
42        stack: &mut Stack,
43        call: &Call,
44        input: PipelineData,
45    ) -> Result<PipelineData, ShellError> {
46        let empty = call.has_flag(engine_state, stack, "empty")?;
47        default(engine_state, stack, call, input, empty)
48    }
49
50    fn examples(&self) -> Vec<Example> {
51        vec![
52            Example {
53                description: "Give a default 'target' column to all file entries",
54                example: "ls -la | default 'nothing' target ",
55                result: None,
56            },
57            Example {
58                description:
59                    "Get the env value of `MY_ENV` with a default value 'abc' if not present",
60                example: "$env | get --ignore-errors MY_ENV | default 'abc'",
61                result: Some(Value::test_string("abc")),
62            },
63            Example {
64                description: "Replace the `null` value in a list",
65                example: "[1, 2, null, 4] | each { default 3 }",
66                result: Some(Value::list(
67                    vec![
68                        Value::test_int(1),
69                        Value::test_int(2),
70                        Value::test_int(3),
71                        Value::test_int(4),
72                    ],
73                    Span::test_data(),
74                )),
75            },
76            Example {
77                description: r#"Replace the missing value in the "a" column of a list"#,
78                example: "[{a:1 b:2} {b:1}] | default 'N/A' a",
79                result: Some(Value::test_list(vec![
80                    Value::test_record(record! {
81                        "a" => Value::test_int(1),
82                        "b" => Value::test_int(2),
83                    }),
84                    Value::test_record(record! {
85                        "a" => Value::test_string("N/A"),
86                        "b" => Value::test_int(1),
87                    }),
88                ])),
89            },
90            Example {
91                description: r#"Replace the empty string in the "a" column of a list"#,
92                example: "[{a:1 b:2} {a:'' b:1}] | default -e 'N/A' a",
93                result: Some(Value::test_list(vec![
94                    Value::test_record(record! {
95                        "a" => Value::test_int(1),
96                        "b" => Value::test_int(2),
97                    }),
98                    Value::test_record(record! {
99                        "a" => Value::test_string("N/A"),
100                        "b" => Value::test_int(1),
101                    }),
102                ])),
103            },
104        ]
105    }
106}
107
108fn default(
109    engine_state: &EngineState,
110    stack: &mut Stack,
111    call: &Call,
112    input: PipelineData,
113    default_when_empty: bool,
114) -> Result<PipelineData, ShellError> {
115    let metadata = input.metadata();
116    let value: Value = call.req(engine_state, stack, 0)?;
117    let column: Option<Spanned<String>> = call.opt(engine_state, stack, 1)?;
118
119    if let Some(column) = column {
120        input
121            .map(
122                move |mut item| match item {
123                    Value::Record {
124                        val: ref mut record,
125                        ..
126                    } => {
127                        let record = record.to_mut();
128                        if let Some(val) = record.get_mut(&column.item) {
129                            if matches!(val, Value::Nothing { .. })
130                                || (default_when_empty && val.is_empty())
131                            {
132                                *val = value.clone();
133                            }
134                        } else {
135                            record.push(column.item.clone(), value.clone());
136                        }
137
138                        item
139                    }
140                    _ => item,
141                },
142                engine_state.signals(),
143            )
144            .map(|x| x.set_metadata(metadata))
145    } else if input.is_nothing()
146        || (default_when_empty
147            && matches!(input, PipelineData::Value(ref value, _) if value.is_empty()))
148    {
149        Ok(value.into_pipeline_data())
150    } else if default_when_empty && matches!(input, PipelineData::ListStream(..)) {
151        let PipelineData::ListStream(ls, metadata) = input else {
152            unreachable!()
153        };
154        let span = ls.span();
155        let mut stream = ls.into_inner().peekable();
156        if stream.peek().is_none() {
157            return Ok(value.into_pipeline_data());
158        }
159
160        // stream's internal state already preserves the original signals config, so if this
161        // Signals::empty list stream gets interrupted it will be caught by the underlying iterator
162        let ls = ListStream::new(stream, span, Signals::empty());
163        Ok(PipelineData::ListStream(ls, metadata))
164    } else {
165        Ok(input)
166    }
167}
168
169#[cfg(test)]
170mod test {
171    use super::*;
172
173    #[test]
174    fn test_examples() {
175        use crate::test_examples;
176
177        test_examples(Default {})
178    }
179}