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 .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 engine_state,
68 call,
69 input,
70 default_value,
71 empty,
72 columns,
73 engine_state.signals(),
74 )
75 }
76
77 fn examples(&self) -> Vec<Example<'_>> {
78 vec![
79 Example {
80 description: "Give a default 'target' column to all file entries",
81 example: "ls -la | default 'nothing' target ",
82 result: None,
83 },
84 Example {
85 description: "Get the env value of `MY_ENV` with a default value 'abc' if not present",
86 example: "$env | get --optional MY_ENV | default 'abc'",
87 result: Some(Value::test_string("abc")),
88 },
89 Example {
90 description: "Replace the `null` value in a list",
91 example: "[1, 2, null, 4] | each { default 3 }",
92 result: Some(Value::list(
93 vec![
94 Value::test_int(1),
95 Value::test_int(2),
96 Value::test_int(3),
97 Value::test_int(4),
98 ],
99 Span::test_data(),
100 )),
101 },
102 Example {
103 description: r#"Replace the missing value in the "a" column of a list"#,
104 example: "[{a:1 b:2} {b:1}] | default 'N/A' a",
105 result: Some(Value::test_list(vec![
106 Value::test_record(record! {
107 "a" => Value::test_int(1),
108 "b" => Value::test_int(2),
109 }),
110 Value::test_record(record! {
111 "a" => Value::test_string("N/A"),
112 "b" => Value::test_int(1),
113 }),
114 ])),
115 },
116 Example {
117 description: r#"Replace the empty string in the "a" column of a list"#,
118 example: "[{a:1 b:2} {a:'' b:1}] | default -e 'N/A' a",
119 result: Some(Value::test_list(vec![
120 Value::test_record(record! {
121 "a" => Value::test_int(1),
122 "b" => Value::test_int(2),
123 }),
124 Value::test_record(record! {
125 "a" => Value::test_string("N/A"),
126 "b" => Value::test_int(1),
127 }),
128 ])),
129 },
130 Example {
131 description: "Generate a default value from a closure",
132 example: "null | default { 1 + 2 }",
133 result: Some(Value::test_int(3)),
134 },
135 Example {
136 description: "Fill missing column values based on other columns",
137 example: "[{a:1 b:2} {b:1}] | upsert a {|rc| default { $rc.b + 1 } }",
138 result: Some(Value::test_list(vec![
139 Value::test_record(record! {
140 "a" => Value::test_int(1),
141 "b" => Value::test_int(2),
142 }),
143 Value::test_record(record! {
144 "a" => Value::test_int(2),
145 "b" => Value::test_int(1),
146 }),
147 ])),
148 },
149 ]
150 }
151}
152
153fn default(
154 engine_state: &EngineState,
155 call: &Call,
156 input: PipelineData,
157 mut default_value: DefaultValue,
158 default_when_empty: bool,
159 columns: Vec<String>,
160 signals: &Signals,
161) -> Result<PipelineData, ShellError> {
162 let mut input = if !columns.is_empty() {
163 input.into_stream_or_original(engine_state)
164 } else {
165 input
166 };
167
168 let input_span = input.span().unwrap_or(call.head);
169 let metadata = input.take_metadata();
170
171 if !columns.is_empty() {
174 if let PipelineData::Value(Value::Record { .. }, _) = input {
175 let record = input.into_value(input_span)?.into_record()?;
176 fill_record(
177 record,
178 input_span,
179 &mut default_value,
180 columns.as_slice(),
181 default_when_empty,
182 )
183 .map(|x| x.into_pipeline_data_with_metadata(metadata))
184 } else if matches!(
185 input,
186 PipelineData::ListStream(..) | PipelineData::Value(Value::List { .. }, _)
187 ) {
188 let head = call.head;
192 Ok(input
193 .into_iter()
194 .map(move |item| {
195 let span = item.span();
196 if let Value::Record { val, .. } = item {
197 fill_record(
198 val.into_owned(),
199 span,
200 &mut default_value,
201 columns.as_slice(),
202 default_when_empty,
203 )
204 .unwrap_or_else(|err| Value::error(err, head))
205 } else {
206 item
207 }
208 })
209 .into_pipeline_data_with_metadata(head, signals.clone(), metadata))
210 } else {
212 Err(ShellError::PipelineMismatch {
213 exp_input_type: "record, table".to_string(),
214 dst_span: input_span,
215 src_span: input_span,
216 })
217 }
218 } else if input.is_nothing()
221 || (default_when_empty
222 && matches!(input, PipelineData::Value(ref value, _) if value.is_empty()))
223 {
224 default_value.single_run_pipeline_data()
225 } else if default_when_empty && matches!(input, PipelineData::ListStream(..)) {
226 let PipelineData::ListStream(ls, _) = input else {
227 unreachable!()
228 };
229 let span = ls.span();
230 let mut stream = ls.into_inner().peekable();
231 if stream.peek().is_none() {
232 return default_value.single_run_pipeline_data();
233 }
234
235 let ls = ListStream::new(stream, span, Signals::empty());
238 Ok(PipelineData::list_stream(ls, metadata))
239 } else {
241 Ok(input.set_metadata(metadata))
242 }
243}
244
245enum DefaultValue {
247 Uncalculated(Box<Spanned<ClosureEval>>),
248 Calculated(Value),
249}
250
251impl DefaultValue {
252 fn new(
253 engine_state: &EngineState,
254 stack: &Stack,
255 value: Value,
256 expr: Option<&Expression>,
257 ) -> Self {
258 let span = value.span();
259
260 let value = match closure_variable_warning(stack, engine_state, value, expr) {
262 Ok(val) => val,
263 Err(default_value) => return default_value,
264 };
265
266 match value {
267 Value::Closure { val, .. } => {
268 let closure_eval = ClosureEval::new(engine_state, stack, *val);
269 DefaultValue::Uncalculated(Box::new(closure_eval.into_spanned(span)))
270 }
271 _ => DefaultValue::Calculated(value),
272 }
273 }
274
275 fn value(&mut self) -> Result<Value, ShellError> {
276 match self {
277 DefaultValue::Uncalculated(closure) => {
278 let value = closure
279 .item
280 .run_with_input(PipelineData::empty())?
281 .into_value(closure.span)?;
282 *self = DefaultValue::Calculated(value.clone());
283 Ok(value)
284 }
285 DefaultValue::Calculated(value) => Ok(value.clone()),
286 }
287 }
288
289 fn single_run_pipeline_data(self) -> Result<PipelineData, ShellError> {
291 match self {
292 DefaultValue::Uncalculated(mut closure) => {
293 closure.item.run_with_input(PipelineData::empty())
294 }
295 DefaultValue::Calculated(val) => Ok(val.into_pipeline_data()),
296 }
297 }
298}
299
300fn fill_record(
302 mut record: Record,
303 span: Span,
304 default_value: &mut DefaultValue,
305 columns: &[String],
306 empty: bool,
307) -> Result<Value, ShellError> {
308 for col in columns {
309 if let Some(val) = record.get_mut(col) {
310 if matches!(val, Value::Nothing { .. }) || (empty && val.is_empty()) {
311 *val = default_value.value()?;
312 }
313 } else {
314 record.push(col.clone(), default_value.value()?);
315 }
316 }
317 Ok(Value::record(record, span))
318}
319
320fn closure_variable_warning(
321 stack: &Stack,
322 engine_state: &EngineState,
323 value: Value,
324 value_expr: Option<&Expression>,
325) -> Result<Value, DefaultValue> {
326 let from_variable = matches!(
328 value_expr,
329 Some(Expression {
330 expr: Expr::FullCellPath(_),
331 ..
332 })
333 );
334
335 let span = value.span();
336 match (&value, from_variable) {
337 (Value::Closure { .. }, true) => {
339 let span_contents = String::from_utf8_lossy(engine_state.get_span_contents(span));
340 let carapace_suggestion = "re-run carapace init with version v1.3.3 or later\nor, change this to `{ $carapace_completer }`";
341 let label = match span_contents {
342 Cow::Borrowed("$carapace_completer") => carapace_suggestion.to_string(),
343 Cow::Owned(s) if s.deref() == "$carapace_completer" => {
344 carapace_suggestion.to_string()
345 }
346 _ => format!("change this to {{ {span_contents} }}").to_string(),
347 };
348
349 report_shell_warning(
350 Some(stack),
351 engine_state,
352 &ShellWarning::Deprecated {
353 dep_type: "Behavior".to_string(),
354 label,
355 span,
356 help: Some(
357 "Since 0.105.0, closure literals passed to default are lazily evaluated, rather than returned as a value.
358In a future release, closures passed by variable will also be lazily evaluated.".to_string(),
359 ),
360 report_mode: ReportMode::FirstUse,
361 },
362 );
363
364 Err(DefaultValue::Calculated(value))
366 }
367 _ => Ok(value),
368 }
369}
370
371#[cfg(test)]
372mod test {
373 use super::*;
374
375 #[test]
376 fn test_examples() -> nu_test_support::Result {
377 nu_test_support::test().examples(Default)
378 }
379}