mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
Add insert and update back (#4864)
This commit is contained in:
parent
6e69d40bb9
commit
0986eefb64
14 changed files with 624 additions and 56 deletions
|
@ -76,6 +76,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
|||
Group,
|
||||
GroupBy,
|
||||
Headers,
|
||||
Insert,
|
||||
SplitBy,
|
||||
Keep,
|
||||
Merge,
|
||||
|
@ -107,6 +108,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
|||
Transpose,
|
||||
Uniq,
|
||||
Upsert,
|
||||
Update,
|
||||
UpdateCells,
|
||||
Where,
|
||||
Window,
|
||||
|
@ -356,7 +358,6 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
|||
|
||||
// Deprecated
|
||||
bind_command! {
|
||||
InsertDeprecated,
|
||||
PivotDeprecated,
|
||||
StrDatetimeDeprecated,
|
||||
StrDecimalDeprecated,
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, PipelineData, Signature,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InsertDeprecated;
|
||||
|
||||
impl Command for InsertDeprecated {
|
||||
fn name(&self) -> &str {
|
||||
"insert"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name()).category(Category::Deprecated)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Deprecated command"
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Err(nu_protocol::ShellError::DeprecatedCommand(
|
||||
self.name().to_string(),
|
||||
"update".to_string(),
|
||||
call.head,
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
mod insert;
|
||||
mod match_;
|
||||
mod nth;
|
||||
mod pivot;
|
||||
|
@ -7,7 +6,6 @@ mod str_decimal;
|
|||
mod str_int;
|
||||
mod unalias;
|
||||
|
||||
pub use insert::InsertDeprecated;
|
||||
pub use match_::MatchDeprecated;
|
||||
pub use nth::NthDeprecated;
|
||||
pub use pivot::PivotDeprecated;
|
||||
|
|
149
crates/nu-command/src/filters/insert.rs
Normal file
149
crates/nu-command/src/filters/insert.rs
Normal file
|
@ -0,0 +1,149 @@
|
|||
use nu_engine::{eval_block, CallExt};
|
||||
use nu_protocol::ast::{Call, CellPath};
|
||||
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, FromValue, IntoPipelineData, PipelineData, ShellError, Signature, Span,
|
||||
SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Insert;
|
||||
|
||||
impl Command for Insert {
|
||||
fn name(&self) -> &str {
|
||||
"insert"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("insert")
|
||||
.required(
|
||||
"field",
|
||||
SyntaxShape::CellPath,
|
||||
"the name of the column to insert",
|
||||
)
|
||||
.required(
|
||||
"new value",
|
||||
SyntaxShape::Any,
|
||||
"the new value to give the cell(s)",
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Insert a new column."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
insert(engine_state, stack, call, input)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Insert a new value",
|
||||
example: "echo {'name': 'nu', 'stars': 5} | insert alias 'Nushell'",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["name".into(), "stars".into(), "alias".into()],
|
||||
vals: vec![
|
||||
Value::test_string("nu"),
|
||||
Value::test_int(5),
|
||||
Value::test_string("Nushell"),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
|
||||
let cell_path: CellPath = call.req(engine_state, stack, 0)?;
|
||||
let replacement: Value = call.req(engine_state, stack, 1)?;
|
||||
|
||||
let redirect_stdout = call.redirect_stdout;
|
||||
let redirect_stderr = call.redirect_stderr;
|
||||
|
||||
let engine_state = engine_state.clone();
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
|
||||
// Replace is a block, so set it up and run it instead of using it as the replacement
|
||||
if replacement.as_block().is_ok() {
|
||||
let capture_block: CaptureBlock = FromValue::from_value(&replacement)?;
|
||||
let block = engine_state.get_block(capture_block.block_id).clone();
|
||||
|
||||
let mut stack = stack.captures_to_stack(&capture_block.captures);
|
||||
let orig_env_vars = stack.env_vars.clone();
|
||||
let orig_env_hidden = stack.env_hidden.clone();
|
||||
|
||||
input.map(
|
||||
move |mut input| {
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, input.clone())
|
||||
}
|
||||
}
|
||||
|
||||
let output = eval_block(
|
||||
&engine_state,
|
||||
&mut stack,
|
||||
&block,
|
||||
input.clone().into_pipeline_data(),
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
);
|
||||
|
||||
match output {
|
||||
Ok(pd) => {
|
||||
if let Err(e) =
|
||||
input.insert_data_at_cell_path(&cell_path.members, pd.into_value(span))
|
||||
{
|
||||
return Value::Error { error: e };
|
||||
}
|
||||
|
||||
input
|
||||
}
|
||||
Err(e) => Value::Error { error: e },
|
||||
}
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
} else {
|
||||
input.map(
|
||||
move |mut input| {
|
||||
let replacement = replacement.clone();
|
||||
|
||||
if let Err(e) = input.insert_data_at_cell_path(&cell_path.members, replacement) {
|
||||
return Value::Error { error: e };
|
||||
}
|
||||
|
||||
input
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Insert {})
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ mod get;
|
|||
mod group;
|
||||
mod group_by;
|
||||
mod headers;
|
||||
mod insert;
|
||||
mod keep;
|
||||
mod last;
|
||||
mod length;
|
||||
|
@ -38,6 +39,7 @@ mod sort_by;
|
|||
mod split_by;
|
||||
mod transpose;
|
||||
mod uniq;
|
||||
mod update;
|
||||
mod update_cells;
|
||||
mod upsert;
|
||||
mod where_;
|
||||
|
@ -63,6 +65,7 @@ pub use get::Get;
|
|||
pub use group::Group;
|
||||
pub use group_by::GroupBy;
|
||||
pub use headers::Headers;
|
||||
pub use insert::Insert;
|
||||
pub use keep::*;
|
||||
pub use last::Last;
|
||||
pub use length::Length;
|
||||
|
@ -85,6 +88,7 @@ pub use sort_by::SortBy;
|
|||
pub use split_by::SplitBy;
|
||||
pub use transpose::Transpose;
|
||||
pub use uniq::*;
|
||||
pub use update::Update;
|
||||
pub use update_cells::UpdateCells;
|
||||
pub use upsert::Upsert;
|
||||
pub use where_::Where;
|
||||
|
|
159
crates/nu-command/src/filters/update.rs
Normal file
159
crates/nu-command/src/filters/update.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
use nu_engine::{eval_block, CallExt};
|
||||
use nu_protocol::ast::{Call, CellPath};
|
||||
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, FromValue, IntoPipelineData, PipelineData, ShellError, Signature, Span,
|
||||
SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Update;
|
||||
|
||||
impl Command for Update {
|
||||
fn name(&self) -> &str {
|
||||
"update"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("update")
|
||||
.required(
|
||||
"field",
|
||||
SyntaxShape::CellPath,
|
||||
"the name of the column to update",
|
||||
)
|
||||
.required(
|
||||
"replacement value",
|
||||
SyntaxShape::Any,
|
||||
"the new value to give the cell(s)",
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Update an existing column to have a new value."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
upsert(engine_state, stack, call, input)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Update a column value",
|
||||
example: "echo {'name': 'nu', 'stars': 5} | update name 'Nushell'",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["name".into(), "stars".into()],
|
||||
vals: vec![Value::test_string("Nushell"), Value::test_int(5)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Use in block form for more involved updating logic",
|
||||
example: "echo [[count fruit]; [1 'apple']] | update count {|f| $f.count + 1}",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: vec!["count".into(), "fruit".into()],
|
||||
vals: vec![Value::test_int(2), Value::test_string("apple")],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn upsert(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
|
||||
let cell_path: CellPath = call.req(engine_state, stack, 0)?;
|
||||
let replacement: Value = call.req(engine_state, stack, 1)?;
|
||||
|
||||
let redirect_stdout = call.redirect_stdout;
|
||||
let redirect_stderr = call.redirect_stderr;
|
||||
|
||||
let engine_state = engine_state.clone();
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
|
||||
// Replace is a block, so set it up and run it instead of using it as the replacement
|
||||
if replacement.as_block().is_ok() {
|
||||
let capture_block: CaptureBlock = FromValue::from_value(&replacement)?;
|
||||
let block = engine_state.get_block(capture_block.block_id).clone();
|
||||
|
||||
let mut stack = stack.captures_to_stack(&capture_block.captures);
|
||||
let orig_env_vars = stack.env_vars.clone();
|
||||
let orig_env_hidden = stack.env_hidden.clone();
|
||||
|
||||
input.map(
|
||||
move |mut input| {
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, input.clone())
|
||||
}
|
||||
}
|
||||
|
||||
let output = eval_block(
|
||||
&engine_state,
|
||||
&mut stack,
|
||||
&block,
|
||||
input.clone().into_pipeline_data(),
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
);
|
||||
|
||||
match output {
|
||||
Ok(pd) => {
|
||||
if let Err(e) =
|
||||
input.update_data_at_cell_path(&cell_path.members, pd.into_value(span))
|
||||
{
|
||||
return Value::Error { error: e };
|
||||
}
|
||||
|
||||
input
|
||||
}
|
||||
Err(e) => Value::Error { error: e },
|
||||
}
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
} else {
|
||||
input.map(
|
||||
move |mut input| {
|
||||
let replacement = replacement.clone();
|
||||
|
||||
if let Err(e) = input.update_data_at_cell_path(&cell_path.members, replacement) {
|
||||
return Value::Error { error: e };
|
||||
}
|
||||
|
||||
input
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Update {})
|
||||
}
|
||||
}
|
|
@ -112,7 +112,7 @@ fn upsert(
|
|||
match output {
|
||||
Ok(pd) => {
|
||||
if let Err(e) =
|
||||
input.replace_data_at_cell_path(&cell_path.members, pd.into_value(span))
|
||||
input.upsert_data_at_cell_path(&cell_path.members, pd.into_value(span))
|
||||
{
|
||||
return Value::Error { error: e };
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ fn upsert(
|
|||
move |mut input| {
|
||||
let replacement = replacement.clone();
|
||||
|
||||
if let Err(e) = input.replace_data_at_cell_path(&cell_path.members, replacement) {
|
||||
if let Err(e) = input.upsert_data_at_cell_path(&cell_path.members, replacement) {
|
||||
return Value::Error { error: e };
|
||||
}
|
||||
|
||||
|
|
28
crates/nu-command/tests/commands/insert.rs
Normal file
28
crates/nu-command/tests/commands/insert.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn insert_the_column() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open cargo_sample.toml
|
||||
| insert dev-dependencies.new_assertions "0.7.0"
|
||||
| get dev-dependencies.new_assertions
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "0.7.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_the_column_conflict() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open cargo_sample.toml
|
||||
| insert dev-dependencies.pretty_assertions "0.7.0"
|
||||
"#
|
||||
));
|
||||
|
||||
assert!(actual.err.contains("column already exists"));
|
||||
}
|
|
@ -23,6 +23,7 @@ mod hash_;
|
|||
mod headers;
|
||||
mod help;
|
||||
mod histogram;
|
||||
mod insert;
|
||||
mod into_filesize;
|
||||
mod into_int;
|
||||
mod keep;
|
||||
|
@ -61,6 +62,7 @@ mod str_;
|
|||
mod touch;
|
||||
mod uniq;
|
||||
mod update;
|
||||
mod upsert;
|
||||
mod use_;
|
||||
mod where_;
|
||||
#[cfg(feature = "which")]
|
||||
|
|
|
@ -6,7 +6,7 @@ fn sets_the_column() {
|
|||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open cargo_sample.toml
|
||||
| upsert dev-dependencies.pretty_assertions "0.7.0"
|
||||
| update dev-dependencies.pretty_assertions "0.7.0"
|
||||
| get dev-dependencies.pretty_assertions
|
||||
"#
|
||||
));
|
||||
|
@ -21,7 +21,7 @@ fn sets_the_column_from_a_block_run_output() {
|
|||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open cargo_sample.toml
|
||||
| upsert dev-dependencies.pretty_assertions { open cargo_sample.toml | get dev-dependencies.pretty_assertions | inc --minor }
|
||||
| update dev-dependencies.pretty_assertions { open cargo_sample.toml | get dev-dependencies.pretty_assertions | inc --minor }
|
||||
| get dev-dependencies.pretty_assertions
|
||||
"#
|
||||
));
|
||||
|
@ -35,7 +35,7 @@ fn sets_the_column_from_a_block_full_stream_output() {
|
|||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
wrap content
|
||||
| upsert content { open --raw cargo_sample.toml | lines | first 5 }
|
||||
| update content { open --raw cargo_sample.toml | lines | first 5 }
|
||||
| get content.1
|
||||
| str contains "nu"
|
||||
"#
|
||||
|
@ -50,7 +50,7 @@ fn sets_the_column_from_a_subexpression() {
|
|||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
wrap content
|
||||
| upsert content (open --raw cargo_sample.toml | lines | first 5)
|
||||
| update content (open --raw cargo_sample.toml | lines | first 5)
|
||||
| get content.1
|
||||
| str contains "nu"
|
||||
"#
|
||||
|
@ -58,3 +58,16 @@ fn sets_the_column_from_a_subexpression() {
|
|||
|
||||
assert_eq!(actual.out, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upsert_column_missing() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open cargo_sample.toml
|
||||
| update dev-dependencies.new_assertions "0.7.0"
|
||||
"#
|
||||
));
|
||||
|
||||
assert!(actual.err.contains("cannot find column"));
|
||||
}
|
||||
|
|
60
crates/nu-command/tests/commands/upsert.rs
Normal file
60
crates/nu-command/tests/commands/upsert.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn sets_the_column() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open cargo_sample.toml
|
||||
| upsert dev-dependencies.pretty_assertions "0.7.0"
|
||||
| get dev-dependencies.pretty_assertions
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "0.7.0");
|
||||
}
|
||||
|
||||
#[cfg(features = "inc")]
|
||||
#[test]
|
||||
fn sets_the_column_from_a_block_run_output() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open cargo_sample.toml
|
||||
| upsert dev-dependencies.pretty_assertions { open cargo_sample.toml | get dev-dependencies.pretty_assertions | inc --minor }
|
||||
| get dev-dependencies.pretty_assertions
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "0.7.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sets_the_column_from_a_block_full_stream_output() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
wrap content
|
||||
| upsert content { open --raw cargo_sample.toml | lines | first 5 }
|
||||
| get content.1
|
||||
| str contains "nu"
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sets_the_column_from_a_subexpression() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
wrap content
|
||||
| upsert content (open --raw cargo_sample.toml | lines | first 5)
|
||||
| get content.1
|
||||
| str contains "nu"
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
}
|
|
@ -226,7 +226,7 @@ impl PipelineData {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_cell_path(
|
||||
pub fn upsert_cell_path(
|
||||
&mut self,
|
||||
cell_path: &[PathMember],
|
||||
callback: Box<dyn FnOnce(&Value) -> Value>,
|
||||
|
@ -238,8 +238,8 @@ impl PipelineData {
|
|||
vals: stream.collect(),
|
||||
span: head,
|
||||
}
|
||||
.update_cell_path(cell_path, callback),
|
||||
PipelineData::Value(v, ..) => v.update_cell_path(cell_path, callback),
|
||||
.upsert_cell_path(cell_path, callback),
|
||||
PipelineData::Value(v, ..) => v.upsert_cell_path(cell_path, callback),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,6 +150,13 @@ Either make sure {0} is a string, or add a 'to_string' entry for it in ENV_CONVE
|
|||
#[label = "value originates here"] Span,
|
||||
),
|
||||
|
||||
#[error("Column already exists")]
|
||||
#[diagnostic(code(nu::shell::column_already_exists), url(docsrs))]
|
||||
ColumnAlreadyExists(
|
||||
#[label = "column already exists"] Span,
|
||||
#[label = "value originates here"] Span,
|
||||
),
|
||||
|
||||
#[error("Not a list value")]
|
||||
#[diagnostic(code(nu::shell::not_a_list), url(docsrs))]
|
||||
NotAList(
|
||||
|
|
|
@ -682,7 +682,7 @@ impl Value {
|
|||
}
|
||||
|
||||
/// Follow a given column path into the value: for example accessing select elements in a stream or list
|
||||
pub fn update_cell_path(
|
||||
pub fn upsert_cell_path(
|
||||
&mut self,
|
||||
cell_path: &[PathMember],
|
||||
callback: Box<dyn FnOnce(&Value) -> Value>,
|
||||
|
@ -693,11 +693,11 @@ impl Value {
|
|||
|
||||
match new_val {
|
||||
Value::Error { error } => Err(error),
|
||||
new_val => self.replace_data_at_cell_path(cell_path, new_val),
|
||||
new_val => self.upsert_data_at_cell_path(cell_path, new_val),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_data_at_cell_path(
|
||||
pub fn upsert_data_at_cell_path(
|
||||
&mut self,
|
||||
cell_path: &[PathMember],
|
||||
new_val: Value,
|
||||
|
@ -716,7 +716,7 @@ impl Value {
|
|||
for col in cols.iter().zip(vals.iter_mut()) {
|
||||
if col.0 == col_name {
|
||||
found = true;
|
||||
col.1.replace_data_at_cell_path(
|
||||
col.1.upsert_data_at_cell_path(
|
||||
&cell_path[1..],
|
||||
new_val.clone(),
|
||||
)?
|
||||
|
@ -733,7 +733,7 @@ impl Value {
|
|||
vals: vec![],
|
||||
span: new_val.span()?,
|
||||
};
|
||||
new_col.replace_data_at_cell_path(
|
||||
new_col.upsert_data_at_cell_path(
|
||||
&cell_path[1..],
|
||||
new_val,
|
||||
)?;
|
||||
|
@ -754,7 +754,7 @@ impl Value {
|
|||
found = true;
|
||||
|
||||
col.1
|
||||
.replace_data_at_cell_path(&cell_path[1..], new_val.clone())?
|
||||
.upsert_data_at_cell_path(&cell_path[1..], new_val.clone())?
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
|
@ -767,7 +767,7 @@ impl Value {
|
|||
vals: vec![],
|
||||
span: new_val.span()?,
|
||||
};
|
||||
new_col.replace_data_at_cell_path(&cell_path[1..], new_val)?;
|
||||
new_col.upsert_data_at_cell_path(&cell_path[1..], new_val)?;
|
||||
vals.push(new_col);
|
||||
}
|
||||
}
|
||||
|
@ -777,7 +777,190 @@ impl Value {
|
|||
PathMember::Int { val: row_num, span } => match self {
|
||||
Value::List { vals, .. } => {
|
||||
if let Some(v) = vals.get_mut(*row_num) {
|
||||
v.replace_data_at_cell_path(&cell_path[1..], new_val)?
|
||||
v.upsert_data_at_cell_path(&cell_path[1..], new_val)?
|
||||
} else {
|
||||
return Err(ShellError::AccessBeyondEnd(vals.len(), *span));
|
||||
}
|
||||
}
|
||||
v => return Err(ShellError::NotAList(*span, v.span()?)),
|
||||
},
|
||||
},
|
||||
None => {
|
||||
*self = new_val;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Follow a given column path into the value: for example accessing select elements in a stream or list
|
||||
pub fn update_cell_path(
|
||||
&mut self,
|
||||
cell_path: &[PathMember],
|
||||
callback: Box<dyn FnOnce(&Value) -> Value>,
|
||||
) -> Result<(), ShellError> {
|
||||
let orig = self.clone();
|
||||
|
||||
let new_val = callback(&orig.follow_cell_path(cell_path)?);
|
||||
|
||||
match new_val {
|
||||
Value::Error { error } => Err(error),
|
||||
new_val => self.update_data_at_cell_path(cell_path, new_val),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_data_at_cell_path(
|
||||
&mut self,
|
||||
cell_path: &[PathMember],
|
||||
new_val: Value,
|
||||
) -> Result<(), ShellError> {
|
||||
match cell_path.first() {
|
||||
Some(path_member) => match path_member {
|
||||
PathMember::String {
|
||||
val: col_name,
|
||||
span,
|
||||
} => match self {
|
||||
Value::List { vals, .. } => {
|
||||
for val in vals.iter_mut() {
|
||||
match val {
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: v_span,
|
||||
} => {
|
||||
let mut found = false;
|
||||
for col in cols.iter().zip(vals.iter_mut()) {
|
||||
if col.0 == col_name {
|
||||
found = true;
|
||||
col.1.update_data_at_cell_path(
|
||||
&cell_path[1..],
|
||||
new_val.clone(),
|
||||
)?
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return Err(ShellError::CantFindColumn(*span, *v_span));
|
||||
}
|
||||
}
|
||||
v => return Err(ShellError::CantFindColumn(*span, v.span()?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: v_span,
|
||||
} => {
|
||||
let mut found = false;
|
||||
|
||||
for col in cols.iter().zip(vals.iter_mut()) {
|
||||
if col.0 == col_name {
|
||||
found = true;
|
||||
|
||||
col.1
|
||||
.update_data_at_cell_path(&cell_path[1..], new_val.clone())?
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return Err(ShellError::CantFindColumn(*span, *v_span));
|
||||
}
|
||||
}
|
||||
v => return Err(ShellError::CantFindColumn(*span, v.span()?)),
|
||||
},
|
||||
PathMember::Int { val: row_num, span } => match self {
|
||||
Value::List { vals, .. } => {
|
||||
if let Some(v) = vals.get_mut(*row_num) {
|
||||
v.update_data_at_cell_path(&cell_path[1..], new_val)?
|
||||
} else {
|
||||
return Err(ShellError::AccessBeyondEnd(vals.len(), *span));
|
||||
}
|
||||
}
|
||||
v => return Err(ShellError::NotAList(*span, v.span()?)),
|
||||
},
|
||||
},
|
||||
None => {
|
||||
*self = new_val;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn insert_data_at_cell_path(
|
||||
&mut self,
|
||||
cell_path: &[PathMember],
|
||||
new_val: Value,
|
||||
) -> Result<(), ShellError> {
|
||||
match cell_path.first() {
|
||||
Some(path_member) => match path_member {
|
||||
PathMember::String {
|
||||
val: col_name,
|
||||
span,
|
||||
} => match self {
|
||||
Value::List { vals, .. } => {
|
||||
for val in vals.iter_mut() {
|
||||
match val {
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: v_span,
|
||||
} => {
|
||||
for col in cols.iter().zip(vals.iter_mut()) {
|
||||
if col.0 == col_name {
|
||||
if cell_path.len() == 1 {
|
||||
return Err(ShellError::ColumnAlreadyExists(
|
||||
*span, *v_span,
|
||||
));
|
||||
} else {
|
||||
return col.1.insert_data_at_cell_path(
|
||||
&cell_path[1..],
|
||||
new_val,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cols.push(col_name.clone());
|
||||
vals.push(new_val.clone());
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"table or record".into(),
|
||||
*span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: v_span,
|
||||
} => {
|
||||
for col in cols.iter().zip(vals.iter_mut()) {
|
||||
if col.0 == col_name {
|
||||
if cell_path.len() == 1 {
|
||||
return Err(ShellError::ColumnAlreadyExists(*span, *v_span));
|
||||
} else {
|
||||
return col
|
||||
.1
|
||||
.insert_data_at_cell_path(&cell_path[1..], new_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cols.push(col_name.clone());
|
||||
vals.push(new_val);
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"table or record".into(),
|
||||
*span,
|
||||
))
|
||||
}
|
||||
},
|
||||
PathMember::Int { val: row_num, span } => match self {
|
||||
Value::List { vals, .. } => {
|
||||
if let Some(v) = vals.get_mut(*row_num) {
|
||||
v.insert_data_at_cell_path(&cell_path[1..], new_val)?
|
||||
} else {
|
||||
return Err(ShellError::AccessBeyondEnd(vals.len(), *span));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue