1use std::{borrow::Cow, ops::Deref};
2
3use nu_engine::{ClosureEval, command_prelude::*};
4use nu_protocol::{
5 ListStream, 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 .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 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 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 --ignore-errors 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 !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 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 } 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 } else if input.is_nothing()
213 || (default_when_empty
214 && matches!(input, PipelineData::Value(ref value, _) if value.is_empty()))
215 {
216 default_value.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.pipeline_data();
225 }
226
227 let ls = ListStream::new(stream, span, Signals::empty());
230 Ok(PipelineData::ListStream(ls, metadata))
231 } else {
233 Ok(input)
234 }
235}
236
237enum DefaultValue {
239 Uncalculated(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 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(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 fn pipeline_data(&mut self) -> Result<PipelineData, ShellError> {
282 self.value().map(|x| x.into_pipeline_data())
283 }
284}
285
286fn fill_record(
288 mut record: Record,
289 span: Span,
290 default_value: &mut DefaultValue,
291 columns: &[String],
292 empty: bool,
293) -> Result<Value, ShellError> {
294 for col in columns {
295 if let Some(val) = record.get_mut(col) {
296 if matches!(val, Value::Nothing { .. }) || (empty && val.is_empty()) {
297 *val = default_value.value()?;
298 }
299 } else {
300 record.push(col.clone(), default_value.value()?);
301 }
302 }
303 Ok(Value::record(record, span))
304}
305
306fn closure_variable_warning(
307 engine_state: &EngineState,
308 value: Value,
309 value_expr: Option<&Expression>,
310) -> Result<Value, DefaultValue> {
311 let from_variable = matches!(
313 value_expr,
314 Some(Expression {
315 expr: Expr::FullCellPath(_),
316 ..
317 })
318 );
319
320 let span = value.span();
321 match (&value, from_variable) {
322 (Value::Closure { .. }, true) => {
324 let span_contents = String::from_utf8_lossy(engine_state.get_span_contents(span));
325 let carapace_suggestion = "re-run carapace init with version v1.3.3 or later\nor, change this to `{ $carapace_completer }`";
326 let suggestion = match span_contents {
327 Cow::Borrowed("$carapace_completer") => carapace_suggestion.to_string(),
328 Cow::Owned(s) if s.deref() == "$carapace_completer" => {
329 carapace_suggestion.to_string()
330 }
331 _ => format!("change this to {{ {} }}", span_contents).to_string(),
332 };
333
334 report_shell_warning(
335 engine_state,
336 &ShellError::DeprecationWarning {
337 deprecation_type: "Behavior",
338 suggestion,
339 span,
340 help: Some(
341 r"Since 0.105.0, closure literals passed to default are lazily evaluated, rather than returned as a value.
342In a future release, closures passed by variable will also be lazily evaluated.",
343 ),
344 },
345 );
346
347 Err(DefaultValue::Calculated(value))
349 }
350 _ => Ok(value),
351 }
352}
353
354#[cfg(test)]
355mod test {
356 use crate::Upsert;
357
358 use super::*;
359
360 #[test]
361 fn test_examples() {
362 use crate::test_examples_with_commands;
363
364 test_examples_with_commands(Default {}, &[&Upsert]);
365 }
366}