allow update cells to work on single records (#16018)

# Description
The `update cells` command used to "work" on records by converting them
into single-row tables in the past, but strengthened input type checking
made it so that no longer worked. This commit introduces correct record
-> record functionality.

# User-Facing Changes
Users can now pipe records into `update cells`. An example inspired by a
conversation in the Discord:
```nushell
> version | update cells { split row ', ' } -c [features, installed_plugins]
╭────────────────────┬──────────────────────────────────────────╮
│ version            │ 0.105.2                                  │
│ major              │ 0                                        │
│ minor              │ 105                                      │
│ patch              │ 2                                        │
│ branch             │ update-cells-record                      │
│ commit_hash        │ 4f7e9aac62 │
│ build_os           │ macos-x86_64                             │
│ build_target       │ x86_64-apple-darwin                      │
│ rust_version       │ rustc 1.85.1 (4eb161250 2025-03-15)      │
│ rust_channel       │ 1.85.1-x86_64-apple-darwin               │
│ cargo_version      │ cargo 1.85.1 (d73d2caf9 2024-12-31)      │
│ build_time         │ 2025-06-21 12:02:06 -04:00               │
│ build_rust_channel │ debug                                    │
│ allocator          │ standard                                 │
│                    │ ╭───┬───────────────╮                    │
│ features           │ │ 0 │ default       │                    │
│                    │ │ 1 │ plugin        │                    │
│                    │ │ 2 │ rustls-tls    │                    │
│                    │ │ 3 │ sqlite        │                    │
│                    │ │ 4 │ trash-support │                    │
│                    │ ╰───┴───────────────╯                    │
│                    │ ╭───┬─────────────────╮                  │
│ installed_plugins  │ │ 0 │ formats 0.104.0 │                  │
│                    │ │ 1 │ polars 0.104.0  │                  │
│                    │ │ 2 │ query 0.104.0   │                  │
│                    │ │ 3 │ todotxt 0.3.0   │                  │
│                    │ ╰───┴─────────────────╯                  │
╰────────────────────┴──────────────────────────────────────────╯
```

# Tests + Formatting
👍. Let me know if more tests besides the new example are needed.

# After Submitting
This commit is contained in:
Firegem
2025-06-21 21:53:45 -04:00
committed by GitHub
parent 2a8364d259
commit 574106bc03

View File

@ -12,7 +12,10 @@ impl Command for UpdateCells {
fn signature(&self) -> Signature {
Signature::build("update cells")
.input_output_types(vec![(Type::table(), Type::table())])
.input_output_types(vec![
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.required(
"closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
@ -77,6 +80,15 @@ impl Command for UpdateCells {
"2021-11-18" => Value::test_string(""),
})])),
},
Example {
example: r#"{a: 1, b: 2, c: 3} | update cells { $in + 10 }"#,
description: "Update each value in a record.",
result: Some(Value::test_record(record! {
"a" => Value::test_int(11),
"b" => Value::test_int(12),
"c" => Value::test_int(13),
})),
},
]
}
@ -85,7 +97,7 @@ impl Command for UpdateCells {
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
mut input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let closure: Closure = call.req(engine_state, stack, 0)?;
@ -102,14 +114,51 @@ impl Command for UpdateCells {
let metadata = input.metadata();
Ok(UpdateCellIterator {
iter: input.into_iter(),
closure: ClosureEval::new(engine_state, stack, closure),
columns,
span: head,
match input {
PipelineData::Value(
Value::Record {
ref mut val,
internal_span,
},
..,
) => {
let val = val.to_mut();
update_record(
val,
&mut ClosureEval::new(engine_state, stack, closure),
internal_span,
columns.as_ref(),
);
Ok(input)
}
_ => Ok(UpdateCellIterator {
iter: input.into_iter(),
closure: ClosureEval::new(engine_state, stack, closure),
columns,
span: head,
}
.into_pipeline_data(head, engine_state.signals().clone())
.set_metadata(metadata)),
}
}
}
fn update_record(
record: &mut Record,
closure: &mut ClosureEval,
span: Span,
cols: Option<&HashSet<String>>,
) {
if let Some(columns) = cols {
for (col, val) in record.iter_mut() {
if columns.contains(col) {
*val = eval_value(closure, span, std::mem::take(val));
}
}
} else {
for (_, val) in record.iter_mut() {
*val = eval_value(closure, span, std::mem::take(val))
}
.into_pipeline_data(head, engine_state.signals().clone())
.set_metadata(metadata))
}
}
@ -128,18 +177,7 @@ impl Iterator for UpdateCellIterator {
let value = if let Value::Record { val, .. } = &mut value {
let val = val.to_mut();
if let Some(columns) = &self.columns {
for (col, val) in val.iter_mut() {
if columns.contains(col) {
*val = eval_value(&mut self.closure, self.span, std::mem::take(val));
}
}
} else {
for (_, val) in val.iter_mut() {
*val = eval_value(&mut self.closure, self.span, std::mem::take(val))
}
}
update_record(val, &mut self.closure, self.span, self.columns.as_ref());
value
} else {
eval_value(&mut self.closure, self.span, value)