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 (Type::Custom("matrix".into()), Type::Custom("matrix".into())),
22 ])
23 .required(
24 "field",
25 SyntaxShape::CellPath,
26 "The name of the column to update.",
27 )
28 .required(
29 "replacement value",
30 SyntaxShape::Any,
31 "The new value to give the cell(s), or a closure to create the value.",
32 )
33 .allow_variants_without_examples(true)
34 .category(Category::Filters)
35 }
36
37 fn description(&self) -> &str {
38 "Update an existing column to have a new value."
39 }
40
41 fn extra_description(&self) -> &str {
42 "When updating a column, the closure will be run for each row, and the current row will be passed as the first argument. \
43Referencing `$in` inside the closure will provide the value at the column for the current row.
44
45When 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."
46 }
47
48 fn run(
49 &self,
50 engine_state: &EngineState,
51 stack: &mut Stack,
52 call: &Call,
53 input: PipelineData,
54 ) -> Result<PipelineData, ShellError> {
55 update(engine_state, stack, call, input)
56 }
57
58 fn examples(&self) -> Vec<Example<'_>> {
59 vec![
60 Example {
61 description: "Update a column value.",
62 example: "{'name': 'nu', 'stars': 5} | update name 'Nushell'",
63 result: Some(Value::test_record(record! {
64 "name" => Value::test_string("Nushell"),
65 "stars" => Value::test_int(5),
66 })),
67 },
68 Example {
69 description: "Use a closure to alter each value in the 'authors' column to a single string.",
70 example: "[[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors {|row| $row.authors | str join ',' }",
71 result: Some(Value::test_list(vec![Value::test_record(record! {
72 "project" => Value::test_string("nu"),
73 "authors" => Value::test_string("Andrés,JT,Yehuda"),
74 })])),
75 },
76 Example {
77 description: "Implicitly use the `$in` value in a closure to update 'authors'.",
78 example: "[[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors { str join ',' }",
79 result: Some(Value::test_list(vec![Value::test_record(record! {
80 "project" => Value::test_string("nu"),
81 "authors" => Value::test_string("Andrés,JT,Yehuda"),
82 })])),
83 },
84 Example {
85 description: "Update a value at an index in a list.",
86 example: "[1 2 3] | update 1 4",
87 result: Some(Value::test_list(vec![
88 Value::test_int(1),
89 Value::test_int(4),
90 Value::test_int(3),
91 ])),
92 },
93 Example {
94 description: "Use a closure to compute a new value at an index.",
95 example: "[1 2 3] | update 1 {|i| $i + 2 }",
96 result: Some(Value::test_list(vec![
97 Value::test_int(1),
98 Value::test_int(4),
99 Value::test_int(3),
100 ])),
101 },
102 Example {
103 description: "Update a row in a matrix",
104 example: "[[1 2] [3 4]] | into matrix | update 0 [5 6] | matrix into-nu | to nuon",
105 result: Some(Value::test_string("[[5.0, 6.0], [3.0, 4.0]]")),
106 },
107 ]
108 }
109}
110
111fn update_recursive(
112 engine_state: &EngineState,
113 stack: &mut Stack,
114 head_span: Span,
115 replacement: Value,
116 input: PipelineData,
117 cell_paths: &[PathMember],
118) -> Result<PipelineData, ShellError> {
119 match input {
120 PipelineData::Value(mut value, metadata) => {
121 if let Value::Closure { val, .. } = replacement {
122 if matches!(cell_paths.first(), Some(PathMember::Int { .. })) {
123 update_single_value_by_closure(
124 &mut value,
125 ClosureEvalOnce::new(engine_state, stack, *val),
126 head_span,
127 cell_paths,
128 false,
129 )?;
130 } else {
131 let mut closure = ClosureEval::new(engine_state, stack, *val);
132 update_value_by_closure(&mut value, &mut closure, head_span, cell_paths)?;
133 }
134 } else {
135 value.update_data_at_cell_path(cell_paths, replacement)?;
136 }
137 Ok(value.into_pipeline_data_with_metadata(metadata))
138 }
139 PipelineData::ListStream(stream, metadata) => {
140 if let Some((
141 &PathMember::Int {
142 val,
143 span: path_span,
144 optional,
145 },
146 path,
147 )) = cell_paths.split_first()
148 {
149 let mut stream = stream.into_iter();
150 let mut pre_elems = vec![];
151
152 for idx in 0..=val {
153 if let Some(v) = stream.next() {
154 pre_elems.push(v);
155 } else if optional {
156 return Ok(pre_elems
157 .into_iter()
158 .chain(stream)
159 .into_pipeline_data_with_metadata(
160 head_span,
161 engine_state.signals().clone(),
162 metadata,
163 ));
164 } else if idx == 0 {
165 return Err(ShellError::AccessEmptyContent { span: path_span });
166 } else {
167 return Err(ShellError::AccessBeyondEnd {
168 max_idx: idx - 1,
169 span: path_span,
170 });
171 }
172 }
173
174 let value = pre_elems.last_mut().expect("one element");
176
177 if let Value::Closure { val, .. } = replacement {
178 update_single_value_by_closure(
179 value,
180 ClosureEvalOnce::new(engine_state, stack, *val),
181 head_span,
182 path,
183 true,
184 )?;
185 } else {
186 value.update_data_at_cell_path(path, replacement)?;
187 }
188
189 Ok(pre_elems
190 .into_iter()
191 .chain(stream)
192 .into_pipeline_data_with_metadata(
193 head_span,
194 engine_state.signals().clone(),
195 metadata,
196 ))
197 } else if let Some(new_cell_paths) = Value::try_put_int_path_member_on_top(cell_paths) {
198 update_recursive(
199 engine_state,
200 stack,
201 head_span,
202 replacement,
203 PipelineData::ListStream(stream, metadata),
204 &new_cell_paths,
205 )
206 } else if let Value::Closure { val, .. } = replacement {
207 let mut closure = ClosureEval::new(engine_state, stack, *val);
208 let cell_paths = cell_paths.to_vec();
209 let stream = stream.map(move |mut value| {
210 let err =
211 update_value_by_closure(&mut value, &mut closure, head_span, &cell_paths);
212
213 if let Err(e) = err {
214 Value::error(e, head_span)
215 } else {
216 value
217 }
218 });
219 Ok(PipelineData::list_stream(stream, metadata))
220 } else {
221 let cell_paths = cell_paths.to_vec();
222 let stream = stream.map(move |mut value| {
223 if let Err(e) = value.update_data_at_cell_path(&cell_paths, replacement.clone())
224 {
225 Value::error(e, head_span)
226 } else {
227 value
228 }
229 });
230
231 Ok(PipelineData::list_stream(stream, metadata))
232 }
233 }
234 PipelineData::Empty => Err(ShellError::IncompatiblePathAccess {
235 type_name: "empty pipeline".to_string(),
236 span: head_span,
237 }),
238 PipelineData::ByteStream(stream, ..) => Err(ShellError::IncompatiblePathAccess {
239 type_name: stream.type_().describe().into(),
240 span: head_span,
241 }),
242 }
243}
244
245fn update(
246 engine_state: &EngineState,
247 stack: &mut Stack,
248 call: &Call,
249 input: PipelineData,
250) -> Result<PipelineData, ShellError> {
251 let head = call.head;
252 let cell_path: CellPath = call.req(engine_state, stack, 0)?;
253 let replacement: Value = call.req(engine_state, stack, 1)?;
254 let is_custom = matches!(&input, PipelineData::Value(Value::Custom { .. }, _));
255 let input = if is_custom {
256 input
257 } else {
258 input.into_stream_or_original(engine_state)
259 };
260
261 update_recursive(
262 engine_state,
263 stack,
264 head,
265 replacement,
266 input,
267 &cell_path.members,
268 )
269}
270
271fn update_value_by_closure(
276 value: &mut Value,
277 closure: &mut ClosureEval,
278 span: Span,
279 cell_path: &[PathMember],
280) -> Result<(), ShellError> {
281 let row_value = value.clone();
282 update_value_by_closure_recursive(value, &row_value, closure, span, cell_path)
283}
284
285fn update_value_by_closure_recursive(
297 value: &mut Value,
298 row_value: &Value,
299 closure: &mut ClosureEval,
300 span: Span,
301 cell_path: &[PathMember],
302) -> Result<(), ShellError> {
303 let Some((member, path)) = cell_path.split_first() else {
306 let new_value = closure
307 .add_arg(row_value.clone())?
308 .run_with_input(value.clone().into_pipeline_data())?
309 .into_value(span)?;
310 *value = new_value;
311 return Ok(());
312 };
313
314 let v_span = value.span();
315
316 match member {
317 PathMember::String {
318 val: col_name,
319 span: path_span,
320 casing,
321 optional,
322 } => {
323 let path_span = Span::new(path_span.start, path_span.end);
325 match value {
326 Value::List { vals, .. } => {
327 for val in vals.to_mut() {
328 let row_context = val.clone();
331 let val_span = val.span();
332
333 match val {
334 Value::Record { val: record, .. } => {
335 if let Some(cell) =
336 record.to_mut().cased_mut(*casing).get_mut(col_name)
337 {
338 update_value_by_closure_recursive(
339 cell,
340 &row_context,
341 closure,
342 path_span,
343 path,
344 )?;
345 } else if !*optional {
346 return Err(ShellError::CantFindColumn {
347 col_name: col_name.clone(),
348 span: Some(path_span),
349 src_span: val_span,
350 });
351 }
352 }
353 Value::Error { error, .. } => return Err(*error.clone()),
354 _ => {
355 if !*optional {
356 return Err(ShellError::CantFindColumn {
357 col_name: col_name.clone(),
358 span: Some(path_span),
359 src_span: val_span,
360 });
361 }
362 }
363 }
364 }
365 }
366 Value::Record { val: record, .. } => {
367 if let Some(cell) = record.to_mut().cased_mut(*casing).get_mut(col_name) {
368 update_value_by_closure_recursive(
369 cell, row_value, closure, path_span, path,
370 )?;
371 } else if !*optional {
372 return Err(ShellError::CantFindColumn {
373 col_name: col_name.clone(),
374 span: Some(path_span),
375 src_span: v_span,
376 });
377 }
378 }
379 Value::Error { error, .. } => return Err(*error.clone()),
380 v => {
381 if !*optional {
382 return Err(ShellError::CantFindColumn {
383 col_name: col_name.clone(),
384 span: Some(path_span),
385 src_span: v.span(),
386 });
387 }
388 }
389 }
390 }
391 PathMember::Int {
392 val: row_num,
393 span: path_span,
394 optional,
395 } => {
396 let path_span = Span::new(path_span.start, path_span.end);
397 match value {
398 Value::List { vals, .. } => {
399 if *row_num < vals.len() {
400 let cell = &mut vals.to_mut()[*row_num];
401 update_value_by_closure_recursive(
402 cell, row_value, closure, path_span, path,
403 )?;
404 } else if !*optional {
405 if vals.is_empty() {
406 return Err(ShellError::AccessEmptyContent { span: path_span });
407 }
408
409 return Err(ShellError::AccessBeyondEnd {
410 max_idx: vals.len() - 1,
411 span: path_span,
412 });
413 }
414 }
415 Value::Error { error, .. } => return Err(*error.clone()),
416 v => {
417 return Err(ShellError::NotAList {
418 dst_span: path_span,
419 src_span: v.span(),
420 });
421 }
422 }
423 }
424 }
425
426 Ok(())
427}
428
429fn update_single_value_by_closure(
430 value: &mut Value,
431 closure: ClosureEvalOnce,
432 span: Span,
433 cell_path: &[PathMember],
434 cell_value_as_arg: bool,
435) -> Result<(), ShellError> {
436 let value_at_path = value.follow_cell_path(cell_path)?;
437
438 let is_optional = cell_path.iter().any(|member| match member {
440 PathMember::String { optional, .. } => *optional,
441 PathMember::Int { optional, .. } => *optional,
442 });
443 if is_optional && matches!(value_at_path.as_ref(), Value::Nothing { .. }) {
444 return Ok(());
445 }
446
447 let arg = if cell_value_as_arg {
451 value_at_path.as_ref()
452 } else {
453 &*value
454 };
455
456 let new_value = closure
457 .add_arg(arg.clone())?
458 .run_with_input(value_at_path.into_owned().into_pipeline_data())?
459 .into_value(span)?;
460
461 value.update_data_at_cell_path(cell_path, new_value)
462}
463
464#[cfg(test)]
465mod test {
466 use super::*;
467
468 #[test]
469 fn test_examples() -> nu_test_support::Result {
470 nu_test_support::test().examples(Update)
471 }
472}