nu_command/filters/
default.rs

1use std::{borrow::Cow, ops::Deref};
2
3use nu_engine::{ClosureEval, command_prelude::*};
4use nu_protocol::{
5    ListStream, ReportMode, ShellWarning, Signals,
6    ast::{Expr, Expression},
7    report_shell_warning,
8};
9
10#[derive(Clone)]
11pub struct Default;
12
13impl Command for Default {
14    fn name(&self) -> &str {
15        "default"
16    }
17
18    fn signature(&self) -> Signature {
19        Signature::build("default")
20            // TODO: Give more specific type signature?
21            // TODO: Declare usage of cell paths in signature? (It seems to behave as if it uses cell paths)
22            .input_output_types(vec![(Type::Any, Type::Any)])
23            .required(
24                "default value",
25                SyntaxShape::Any,
26                "The value to use as a default.",
27            )
28            .rest(
29                "column name",
30                SyntaxShape::String,
31                "The name of the column.",
32            )
33            .switch(
34                "empty",
35                "also replace empty items like \"\", {}, and []",
36                Some('e'),
37            )
38            .category(Category::Filters)
39    }
40
41    // FIXME remove once deprecation warning is no longer needed
42    fn requires_ast_for_arguments(&self) -> bool {
43        true
44    }
45
46    fn description(&self) -> &str {
47        "Sets a default value if a row's column is missing or null."
48    }
49
50    fn run(
51        &self,
52        engine_state: &EngineState,
53        stack: &mut Stack,
54        call: &Call,
55        input: PipelineData,
56    ) -> Result<PipelineData, ShellError> {
57        let default_value: Value = call.req(engine_state, stack, 0)?;
58        let columns: Vec<String> = call.rest(engine_state, stack, 1)?;
59        let empty = call.has_flag(engine_state, stack, "empty")?;
60
61        // FIXME for deprecation of closure passed via variable
62        let default_value_expr = call.positional_nth(stack, 0);
63        let default_value =
64            DefaultValue::new(engine_state, stack, default_value, default_value_expr);
65
66        default(
67            call,
68            input,
69            default_value,
70            empty,
71            columns,
72            engine_state.signals(),
73        )
74    }
75
76    fn examples(&self) -> Vec<Example> {
77        vec![
78            Example {
79                description: "Give a default 'target' column to all file entries",
80                example: "ls -la | default 'nothing' target ",
81                result: None,
82            },
83            Example {
84                description: "Get the env value of `MY_ENV` with a default value 'abc' if not present",
85                example: "$env | get --optional MY_ENV | default 'abc'",
86                result: Some(Value::test_string("abc")),
87            },
88            Example {
89                description: "Replace the `null` value in a list",
90                example: "[1, 2, null, 4] | each { default 3 }",
91                result: Some(Value::list(
92                    vec![
93                        Value::test_int(1),
94                        Value::test_int(2),
95                        Value::test_int(3),
96                        Value::test_int(4),
97                    ],
98                    Span::test_data(),
99                )),
100            },
101            Example {
102                description: r#"Replace the missing value in the "a" column of a list"#,
103                example: "[{a:1 b:2} {b:1}] | default 'N/A' a",
104                result: Some(Value::test_list(vec![
105                    Value::test_record(record! {
106                        "a" => Value::test_int(1),
107                        "b" => Value::test_int(2),
108                    }),
109                    Value::test_record(record! {
110                        "a" => Value::test_string("N/A"),
111                        "b" => Value::test_int(1),
112                    }),
113                ])),
114            },
115            Example {
116                description: r#"Replace the empty string in the "a" column of a list"#,
117                example: "[{a:1 b:2} {a:'' b:1}] | default -e 'N/A' a",
118                result: Some(Value::test_list(vec![
119                    Value::test_record(record! {
120                        "a" => Value::test_int(1),
121                        "b" => Value::test_int(2),
122                    }),
123                    Value::test_record(record! {
124                        "a" => Value::test_string("N/A"),
125                        "b" => Value::test_int(1),
126                    }),
127                ])),
128            },
129            Example {
130                description: r#"Generate a default value from a closure"#,
131                example: "null | default { 1 + 2 }",
132                result: Some(Value::test_int(3)),
133            },
134            Example {
135                description: r#"Fill missing column values based on other columns"#,
136                example: r#"[{a:1 b:2} {b:1}] | upsert a {|rc| default { $rc.b + 1 } }"#,
137                result: Some(Value::test_list(vec![
138                    Value::test_record(record! {
139                        "a" => Value::test_int(1),
140                        "b" => Value::test_int(2),
141                    }),
142                    Value::test_record(record! {
143                        "a" => Value::test_int(2),
144                        "b" => Value::test_int(1),
145                    }),
146                ])),
147            },
148        ]
149    }
150}
151
152fn default(
153    call: &Call,
154    input: PipelineData,
155    mut default_value: DefaultValue,
156    default_when_empty: bool,
157    columns: Vec<String>,
158    signals: &Signals,
159) -> Result<PipelineData, ShellError> {
160    let input_span = input.span().unwrap_or(call.head);
161    let metadata = input.metadata();
162
163    // If user supplies columns, check if input is a record or list of records
164    // and set the default value for the specified record columns
165    if !columns.is_empty() {
166        if matches!(input, PipelineData::Value(Value::Record { .. }, _)) {
167            let record = input.into_value(input_span)?.into_record()?;
168            fill_record(
169                record,
170                input_span,
171                &mut default_value,
172                columns.as_slice(),
173                default_when_empty,
174            )
175            .map(|x| x.into_pipeline_data_with_metadata(metadata))
176        } else if matches!(
177            input,
178            PipelineData::ListStream(..) | PipelineData::Value(Value::List { .. }, _)
179        ) {
180            // Potential enhancement: add another branch for Value::List,
181            // and collect the iterator into a Result<Value::List, ShellError>
182            // so we can preemptively return an error for collected lists
183            let head = call.head;
184            Ok(input
185                .into_iter()
186                .map(move |item| {
187                    let span = item.span();
188                    if let Value::Record { val, .. } = item {
189                        fill_record(
190                            val.into_owned(),
191                            span,
192                            &mut default_value,
193                            columns.as_slice(),
194                            default_when_empty,
195                        )
196                        .unwrap_or_else(|err| Value::error(err, head))
197                    } else {
198                        item
199                    }
200                })
201                .into_pipeline_data_with_metadata(head, signals.clone(), metadata))
202        // If columns are given, but input does not use columns, return an error
203        } else {
204            Err(ShellError::PipelineMismatch {
205                exp_input_type: "record, table".to_string(),
206                dst_span: input_span,
207                src_span: input_span,
208            })
209        }
210    // Otherwise, if no column name is given, check if value is null
211    // or an empty string, list, or record when --empty is passed
212    } else if input.is_nothing()
213        || (default_when_empty
214            && matches!(input, PipelineData::Value(ref value, _) if value.is_empty()))
215    {
216        default_value.single_run_pipeline_data()
217    } else if default_when_empty && matches!(input, PipelineData::ListStream(..)) {
218        let PipelineData::ListStream(ls, metadata) = input else {
219            unreachable!()
220        };
221        let span = ls.span();
222        let mut stream = ls.into_inner().peekable();
223        if stream.peek().is_none() {
224            return default_value.single_run_pipeline_data();
225        }
226
227        // stream's internal state already preserves the original signals config, so if this
228        // Signals::empty list stream gets interrupted it will be caught by the underlying iterator
229        let ls = ListStream::new(stream, span, Signals::empty());
230        Ok(PipelineData::list_stream(ls, metadata))
231    // Otherwise, return the input as is
232    } else {
233        Ok(input)
234    }
235}
236
237/// A wrapper around the default value to handle closures and caching values
238enum DefaultValue {
239    Uncalculated(Box<Spanned<ClosureEval>>),
240    Calculated(Value),
241}
242
243impl DefaultValue {
244    fn new(
245        engine_state: &EngineState,
246        stack: &Stack,
247        value: Value,
248        expr: Option<&Expression>,
249    ) -> Self {
250        let span = value.span();
251
252        // FIXME temporary workaround to warn people of breaking change from #15654
253        let value = match closure_variable_warning(engine_state, value, expr) {
254            Ok(val) => val,
255            Err(default_value) => return default_value,
256        };
257
258        match value {
259            Value::Closure { val, .. } => {
260                let closure_eval = ClosureEval::new(engine_state, stack, *val);
261                DefaultValue::Uncalculated(Box::new(closure_eval.into_spanned(span)))
262            }
263            _ => DefaultValue::Calculated(value),
264        }
265    }
266
267    fn value(&mut self) -> Result<Value, ShellError> {
268        match self {
269            DefaultValue::Uncalculated(closure) => {
270                let value = closure
271                    .item
272                    .run_with_input(PipelineData::empty())?
273                    .into_value(closure.span)?;
274                *self = DefaultValue::Calculated(value.clone());
275                Ok(value)
276            }
277            DefaultValue::Calculated(value) => Ok(value.clone()),
278        }
279    }
280
281    /// Used when we know the value won't need to be cached to allow streaming.
282    fn single_run_pipeline_data(self) -> Result<PipelineData, ShellError> {
283        match self {
284            DefaultValue::Uncalculated(mut closure) => {
285                closure.item.run_with_input(PipelineData::empty())
286            }
287            DefaultValue::Calculated(val) => Ok(val.into_pipeline_data()),
288        }
289    }
290}
291
292/// Given a record, fill missing columns with a default value
293fn fill_record(
294    mut record: Record,
295    span: Span,
296    default_value: &mut DefaultValue,
297    columns: &[String],
298    empty: bool,
299) -> Result<Value, ShellError> {
300    for col in columns {
301        if let Some(val) = record.get_mut(col) {
302            if matches!(val, Value::Nothing { .. }) || (empty && val.is_empty()) {
303                *val = default_value.value()?;
304            }
305        } else {
306            record.push(col.clone(), default_value.value()?);
307        }
308    }
309    Ok(Value::record(record, span))
310}
311
312fn closure_variable_warning(
313    engine_state: &EngineState,
314    value: Value,
315    value_expr: Option<&Expression>,
316) -> Result<Value, DefaultValue> {
317    // only warn if we are passed a closure inside a variable
318    let from_variable = matches!(
319        value_expr,
320        Some(Expression {
321            expr: Expr::FullCellPath(_),
322            ..
323        })
324    );
325
326    let span = value.span();
327    match (&value, from_variable) {
328        // this is a closure from inside a variable
329        (Value::Closure { .. }, true) => {
330            let span_contents = String::from_utf8_lossy(engine_state.get_span_contents(span));
331            let carapace_suggestion = "re-run carapace init with version v1.3.3 or later\nor, change this to `{ $carapace_completer }`";
332            let label = match span_contents {
333                Cow::Borrowed("$carapace_completer") => carapace_suggestion.to_string(),
334                Cow::Owned(s) if s.deref() == "$carapace_completer" => {
335                    carapace_suggestion.to_string()
336                }
337                _ => format!("change this to {{ {span_contents} }}").to_string(),
338            };
339
340            report_shell_warning(
341                engine_state,
342                &ShellWarning::Deprecated {
343                    dep_type: "Behavior".to_string(),
344                    label,
345                    span,
346                    help: Some(
347                        r"Since 0.105.0, closure literals passed to default are lazily evaluated, rather than returned as a value.
348In a future release, closures passed by variable will also be lazily evaluated.".to_string(),
349                    ),
350                    report_mode: ReportMode::FirstUse,
351                },
352            );
353
354            // bypass the normal DefaultValue::new logic
355            Err(DefaultValue::Calculated(value))
356        }
357        _ => Ok(value),
358    }
359}
360
361#[cfg(test)]
362mod test {
363    use crate::Upsert;
364
365    use super::*;
366
367    #[test]
368    fn test_examples() {
369        use crate::test_examples_with_commands;
370
371        test_examples_with_commands(Default {}, &[&Upsert]);
372    }
373}