Skip to main content

nu_command/filters/
update.rs

1use nu_engine::{ClosureEval, ClosureEvalOnce, command_prelude::*};
2use nu_protocol::ast::PathMember;
3
4#[derive(Clone)]
5pub struct Update;
6
7impl Command for Update {
8    fn name(&self) -> &str {
9        "update"
10    }
11
12    fn signature(&self) -> Signature {
13        Signature::build("update")
14            .input_output_types(vec![
15                (Type::record(), Type::record()),
16                (Type::table(), Type::table()),
17                (
18                    Type::List(Box::new(Type::Any)),
19                    Type::List(Box::new(Type::Any)),
20                ),
21            ])
22            .required(
23                "field",
24                SyntaxShape::CellPath,
25                "The name of the column to update.",
26            )
27            .required(
28                "replacement value",
29                SyntaxShape::Any,
30                "The new value to give the cell(s), or a closure to create the value.",
31            )
32            .allow_variants_without_examples(true)
33            .category(Category::Filters)
34    }
35
36    fn description(&self) -> &str {
37        "Update an existing column to have a new value."
38    }
39
40    fn extra_description(&self) -> &str {
41        "When updating a column, the closure will be run for each row, and the current row will be passed as the first argument. \
42Referencing `$in` inside the closure will provide the value at the column for the current row.
43
44When updating a specific index, the closure will instead be run once. The first argument to the closure and the `$in` value will both be the current value at the index."
45    }
46
47    fn run(
48        &self,
49        engine_state: &EngineState,
50        stack: &mut Stack,
51        call: &Call,
52        input: PipelineData,
53    ) -> Result<PipelineData, ShellError> {
54        update(engine_state, stack, call, input)
55    }
56
57    fn examples(&self) -> Vec<Example<'_>> {
58        vec![
59            Example {
60                description: "Update a column value.",
61                example: "{'name': 'nu', 'stars': 5} | update name 'Nushell'",
62                result: Some(Value::test_record(record! {
63                    "name" =>  Value::test_string("Nushell"),
64                    "stars" => Value::test_int(5),
65                })),
66            },
67            Example {
68                description: "Use a closure to alter each value in the 'authors' column to a single string.",
69                example: "[[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors {|row| $row.authors | str join ',' }",
70                result: Some(Value::test_list(vec![Value::test_record(record! {
71                    "project" => Value::test_string("nu"),
72                    "authors" => Value::test_string("Andrés,JT,Yehuda"),
73                })])),
74            },
75            Example {
76                description: "Implicitly use the `$in` value in a closure to update 'authors'.",
77                example: "[[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors { str join ',' }",
78                result: Some(Value::test_list(vec![Value::test_record(record! {
79                    "project" => Value::test_string("nu"),
80                    "authors" => Value::test_string("Andrés,JT,Yehuda"),
81                })])),
82            },
83            Example {
84                description: "Update a value at an index in a list.",
85                example: "[1 2 3] | update 1 4",
86                result: Some(Value::test_list(vec![
87                    Value::test_int(1),
88                    Value::test_int(4),
89                    Value::test_int(3),
90                ])),
91            },
92            Example {
93                description: "Use a closure to compute a new value at an index.",
94                example: "[1 2 3] | update 1 {|i| $i + 2 }",
95                result: Some(Value::test_list(vec![
96                    Value::test_int(1),
97                    Value::test_int(4),
98                    Value::test_int(3),
99                ])),
100            },
101        ]
102    }
103}
104
105fn update_recursive(
106    engine_state: &EngineState,
107    stack: &mut Stack,
108    head_span: Span,
109    replacement: Value,
110    input: PipelineData,
111    cell_paths: &[PathMember],
112) -> Result<PipelineData, ShellError> {
113    match input {
114        PipelineData::Value(mut value, metadata) => {
115            if let Value::Closure { val, .. } = replacement {
116                if matches!(cell_paths.first(), Some(PathMember::Int { .. })) {
117                    update_single_value_by_closure(
118                        &mut value,
119                        ClosureEvalOnce::new(engine_state, stack, *val),
120                        head_span,
121                        cell_paths,
122                        false,
123                    )?;
124                } else {
125                    let mut closure = ClosureEval::new(engine_state, stack, *val);
126                    update_value_by_closure(&mut value, &mut closure, head_span, cell_paths)?;
127                }
128            } else {
129                value.update_data_at_cell_path(cell_paths, replacement)?;
130            }
131            Ok(value.into_pipeline_data_with_metadata(metadata))
132        }
133        PipelineData::ListStream(stream, metadata) => {
134            if let Some((
135                &PathMember::Int {
136                    val,
137                    span: path_span,
138                    optional,
139                },
140                path,
141            )) = cell_paths.split_first()
142            {
143                let mut stream = stream.into_iter();
144                let mut pre_elems = vec![];
145
146                for idx in 0..=val {
147                    if let Some(v) = stream.next() {
148                        pre_elems.push(v);
149                    } else if optional {
150                        return Ok(pre_elems
151                            .into_iter()
152                            .chain(stream)
153                            .into_pipeline_data_with_metadata(
154                                head_span,
155                                engine_state.signals().clone(),
156                                metadata,
157                            ));
158                    } else if idx == 0 {
159                        return Err(ShellError::AccessEmptyContent { span: path_span });
160                    } else {
161                        return Err(ShellError::AccessBeyondEnd {
162                            max_idx: idx - 1,
163                            span: path_span,
164                        });
165                    }
166                }
167
168                // cannot fail since loop above does at least one iteration or returns an error
169                let value = pre_elems.last_mut().expect("one element");
170
171                if let Value::Closure { val, .. } = replacement {
172                    update_single_value_by_closure(
173                        value,
174                        ClosureEvalOnce::new(engine_state, stack, *val),
175                        head_span,
176                        path,
177                        true,
178                    )?;
179                } else {
180                    value.update_data_at_cell_path(path, replacement)?;
181                }
182
183                Ok(pre_elems
184                    .into_iter()
185                    .chain(stream)
186                    .into_pipeline_data_with_metadata(
187                        head_span,
188                        engine_state.signals().clone(),
189                        metadata,
190                    ))
191            } else if let Some(new_cell_paths) = Value::try_put_int_path_member_on_top(cell_paths) {
192                update_recursive(
193                    engine_state,
194                    stack,
195                    head_span,
196                    replacement,
197                    PipelineData::ListStream(stream, metadata),
198                    &new_cell_paths,
199                )
200            } else if let Value::Closure { val, .. } = replacement {
201                let mut closure = ClosureEval::new(engine_state, stack, *val);
202                let cell_paths = cell_paths.to_vec();
203                let stream = stream.map(move |mut value| {
204                    let err =
205                        update_value_by_closure(&mut value, &mut closure, head_span, &cell_paths);
206
207                    if let Err(e) = err {
208                        Value::error(e, head_span)
209                    } else {
210                        value
211                    }
212                });
213                Ok(PipelineData::list_stream(stream, metadata))
214            } else {
215                let cell_paths = cell_paths.to_vec();
216                let stream = stream.map(move |mut value| {
217                    if let Err(e) = value.update_data_at_cell_path(&cell_paths, replacement.clone())
218                    {
219                        Value::error(e, head_span)
220                    } else {
221                        value
222                    }
223                });
224
225                Ok(PipelineData::list_stream(stream, metadata))
226            }
227        }
228        PipelineData::Empty => Err(ShellError::IncompatiblePathAccess {
229            type_name: "empty pipeline".to_string(),
230            span: head_span,
231        }),
232        PipelineData::ByteStream(stream, ..) => Err(ShellError::IncompatiblePathAccess {
233            type_name: stream.type_().describe().into(),
234            span: head_span,
235        }),
236    }
237}
238
239fn update(
240    engine_state: &EngineState,
241    stack: &mut Stack,
242    call: &Call,
243    input: PipelineData,
244) -> Result<PipelineData, ShellError> {
245    let head = call.head;
246    let cell_path: CellPath = call.req(engine_state, stack, 0)?;
247    let replacement: Value = call.req(engine_state, stack, 1)?;
248    let input = input.into_stream_or_original(engine_state);
249
250    update_recursive(
251        engine_state,
252        stack,
253        head,
254        replacement,
255        input,
256        &cell_path.members,
257    )
258}
259
260/// Applies `closure` to every leaf cell reached by following `cell_path` inside `value`.
261///
262/// Captures the initial value as the row context before descending, then delegates to
263/// [`update_value_by_closure_recursive`] for the actual traversal.
264fn update_value_by_closure(
265    value: &mut Value,
266    closure: &mut ClosureEval,
267    span: Span,
268    cell_path: &[PathMember],
269) -> Result<(), ShellError> {
270    let row_value = value.clone();
271    update_value_by_closure_recursive(value, &row_value, closure, span, cell_path)
272}
273
274/// Recursively walks `cell_path` inside `value`, applying `closure` at every leaf cell.
275///
276/// # Parameters
277/// * `value` - The current cell being traversed. At the leaf it is overwritten with the closure result.
278/// * `row_value` - The enclosing *row* passed as the positional argument (`|row|` / `$it`)
279///   to the closure. When descending through a list-of-records this is updated to each
280///   individual nested record, so the closure always receives the most-immediately-enclosing
281///   row rather than a projected list of column values.
282/// * `closure` - The user-supplied closure, evaluated once per leaf cell.
283/// * `span` - Span used when converting the closure output back to a [`Value`].
284/// * `cell_path` - Remaining path members to traverse.
285fn update_value_by_closure_recursive(
286    value: &mut Value,
287    row_value: &Value,
288    closure: &mut ClosureEval,
289    span: Span,
290    cell_path: &[PathMember],
291) -> Result<(), ShellError> {
292    // Base case: no more path to traverse — evaluate the closure at this leaf cell.
293    // `$in` receives the current cell value; the optional positional arg receives the row.
294    let Some((member, path)) = cell_path.split_first() else {
295        let new_value = closure
296            .add_arg(row_value.clone())?
297            .run_with_input(value.clone().into_pipeline_data())?
298            .into_value(span)?;
299        *value = new_value;
300        return Ok(());
301    };
302
303    let v_span = value.span();
304
305    match member {
306        PathMember::String {
307            val: col_name,
308            span: path_span,
309            casing,
310            optional,
311        } => {
312            // Copy the span once per arm so all inner branches share the same value.
313            let path_span = Span::new(path_span.start, path_span.end);
314            match value {
315                Value::List { vals, .. } => {
316                    for val in vals.iter_mut() {
317                        // Each nested record becomes the new row context so that `|row|`
318                        // refers to the immediately-enclosing record, not the outer row.
319                        let row_context = val.clone();
320                        let val_span = val.span();
321
322                        match val {
323                            Value::Record { val: record, .. } => {
324                                if let Some(cell) =
325                                    record.to_mut().cased_mut(*casing).get_mut(col_name)
326                                {
327                                    update_value_by_closure_recursive(
328                                        cell,
329                                        &row_context,
330                                        closure,
331                                        path_span,
332                                        path,
333                                    )?;
334                                } else if !*optional {
335                                    return Err(ShellError::CantFindColumn {
336                                        col_name: col_name.clone(),
337                                        span: Some(path_span),
338                                        src_span: val_span,
339                                    });
340                                }
341                            }
342                            Value::Error { error, .. } => return Err(*error.clone()),
343                            _ => {
344                                if !*optional {
345                                    return Err(ShellError::CantFindColumn {
346                                        col_name: col_name.clone(),
347                                        span: Some(path_span),
348                                        src_span: val_span,
349                                    });
350                                }
351                            }
352                        }
353                    }
354                }
355                Value::Record { val: record, .. } => {
356                    if let Some(cell) = record.to_mut().cased_mut(*casing).get_mut(col_name) {
357                        update_value_by_closure_recursive(
358                            cell, row_value, closure, path_span, path,
359                        )?;
360                    } else if !*optional {
361                        return Err(ShellError::CantFindColumn {
362                            col_name: col_name.clone(),
363                            span: Some(path_span),
364                            src_span: v_span,
365                        });
366                    }
367                }
368                Value::Error { error, .. } => return Err(*error.clone()),
369                v => {
370                    if !*optional {
371                        return Err(ShellError::CantFindColumn {
372                            col_name: col_name.clone(),
373                            span: Some(path_span),
374                            src_span: v.span(),
375                        });
376                    }
377                }
378            }
379        }
380        PathMember::Int {
381            val: row_num,
382            span: path_span,
383            optional,
384        } => {
385            let path_span = Span::new(path_span.start, path_span.end);
386            match value {
387                Value::List { vals, .. } => {
388                    if let Some(cell) = vals.get_mut(*row_num) {
389                        update_value_by_closure_recursive(
390                            cell, row_value, closure, path_span, path,
391                        )?;
392                    } else if !*optional {
393                        if vals.is_empty() {
394                            return Err(ShellError::AccessEmptyContent { span: path_span });
395                        }
396
397                        return Err(ShellError::AccessBeyondEnd {
398                            max_idx: vals.len() - 1,
399                            span: path_span,
400                        });
401                    }
402                }
403                Value::Error { error, .. } => return Err(*error.clone()),
404                v => {
405                    return Err(ShellError::NotAList {
406                        dst_span: path_span,
407                        src_span: v.span(),
408                    });
409                }
410            }
411        }
412    }
413
414    Ok(())
415}
416
417fn update_single_value_by_closure(
418    value: &mut Value,
419    closure: ClosureEvalOnce,
420    span: Span,
421    cell_path: &[PathMember],
422    cell_value_as_arg: bool,
423) -> Result<(), ShellError> {
424    let value_at_path = value.follow_cell_path(cell_path)?;
425
426    // Don't run the closure for optional paths that don't exist
427    let is_optional = cell_path.iter().any(|member| match member {
428        PathMember::String { optional, .. } => *optional,
429        PathMember::Int { optional, .. } => *optional,
430    });
431    if is_optional && matches!(value_at_path.as_ref(), Value::Nothing { .. }) {
432        return Ok(());
433    }
434
435    // FIXME: this leads to inconsistent behaviors between
436    // `{a: b} | update a {|x| print $x}` and
437    // `[{a: b}] | update 0.a {|x| print $x}`
438    let arg = if cell_value_as_arg {
439        value_at_path.as_ref()
440    } else {
441        &*value
442    };
443
444    let new_value = closure
445        .add_arg(arg.clone())?
446        .run_with_input(value_at_path.into_owned().into_pipeline_data())?
447        .into_value(span)?;
448
449    value.update_data_at_cell_path(cell_path, new_value)
450}
451
452#[cfg(test)]
453mod test {
454    use super::*;
455
456    #[test]
457    fn test_examples() -> nu_test_support::Result {
458        nu_test_support::test().examples(Update)
459    }
460}