From 0986eefb64ae250301138ea2b6f9440698f27ad6 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 18 Mar 2022 06:55:02 +1300 Subject: [PATCH] Add insert and update back (#4864) --- crates/nu-command/src/default_context.rs | 3 +- crates/nu-command/src/deprecated/insert.rs | 36 ---- crates/nu-command/src/deprecated/mod.rs | 2 - crates/nu-command/src/filters/insert.rs | 149 +++++++++++++++ crates/nu-command/src/filters/mod.rs | 4 + crates/nu-command/src/filters/update.rs | 159 ++++++++++++++++ crates/nu-command/src/filters/upsert.rs | 4 +- crates/nu-command/tests/commands/insert.rs | 28 +++ crates/nu-command/tests/commands/mod.rs | 2 + crates/nu-command/tests/commands/update.rs | 21 ++- crates/nu-command/tests/commands/upsert.rs | 60 +++++++ crates/nu-protocol/src/pipeline_data.rs | 6 +- crates/nu-protocol/src/shell_error.rs | 7 + crates/nu-protocol/src/value/mod.rs | 199 ++++++++++++++++++++- 14 files changed, 624 insertions(+), 56 deletions(-) delete mode 100644 crates/nu-command/src/deprecated/insert.rs create mode 100644 crates/nu-command/src/filters/insert.rs create mode 100644 crates/nu-command/src/filters/update.rs create mode 100644 crates/nu-command/tests/commands/insert.rs create mode 100644 crates/nu-command/tests/commands/upsert.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 63c423977b..4681db9839 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -76,6 +76,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Group, GroupBy, Headers, + Insert, SplitBy, Keep, Merge, @@ -107,6 +108,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Transpose, Uniq, Upsert, + Update, UpdateCells, Where, Window, @@ -356,7 +358,6 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { // Deprecated bind_command! { - InsertDeprecated, PivotDeprecated, StrDatetimeDeprecated, StrDecimalDeprecated, diff --git a/crates/nu-command/src/deprecated/insert.rs b/crates/nu-command/src/deprecated/insert.rs deleted file mode 100644 index 17075b7598..0000000000 --- a/crates/nu-command/src/deprecated/insert.rs +++ /dev/null @@ -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 { - Err(nu_protocol::ShellError::DeprecatedCommand( - self.name().to_string(), - "update".to_string(), - call.head, - )) - } -} diff --git a/crates/nu-command/src/deprecated/mod.rs b/crates/nu-command/src/deprecated/mod.rs index 09ae1ac324..79bfab90b8 100644 --- a/crates/nu-command/src/deprecated/mod.rs +++ b/crates/nu-command/src/deprecated/mod.rs @@ -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; diff --git a/crates/nu-command/src/filters/insert.rs b/crates/nu-command/src/filters/insert.rs new file mode 100644 index 0000000000..254c7b3d28 --- /dev/null +++ b/crates/nu-command/src/filters/insert.rs @@ -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 { + insert(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + 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 { + 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 {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 07fb7ea251..ca8b184a37 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -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; diff --git a/crates/nu-command/src/filters/update.rs b/crates/nu-command/src/filters/update.rs new file mode 100644 index 0000000000..8f9cd6760b --- /dev/null +++ b/crates/nu-command/src/filters/update.rs @@ -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 { + upsert(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + 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 { + 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 {}) + } +} diff --git a/crates/nu-command/src/filters/upsert.rs b/crates/nu-command/src/filters/upsert.rs index eaa815b3bf..545a45aabd 100644 --- a/crates/nu-command/src/filters/upsert.rs +++ b/crates/nu-command/src/filters/upsert.rs @@ -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 }; } diff --git a/crates/nu-command/tests/commands/insert.rs b/crates/nu-command/tests/commands/insert.rs new file mode 100644 index 0000000000..d935861263 --- /dev/null +++ b/crates/nu-command/tests/commands/insert.rs @@ -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")); +} diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index bb17eddad5..04c1938c90 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -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")] diff --git a/crates/nu-command/tests/commands/update.rs b/crates/nu-command/tests/commands/update.rs index ac8e5c9b1f..f2dace3981 100644 --- a/crates/nu-command/tests/commands/update.rs +++ b/crates/nu-command/tests/commands/update.rs @@ -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")); +} diff --git a/crates/nu-command/tests/commands/upsert.rs b/crates/nu-command/tests/commands/upsert.rs new file mode 100644 index 0000000000..ac8e5c9b1f --- /dev/null +++ b/crates/nu-command/tests/commands/upsert.rs @@ -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"); +} diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index b5db072d52..a2ee759672 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -226,7 +226,7 @@ impl PipelineData { } } - pub fn update_cell_path( + pub fn upsert_cell_path( &mut self, cell_path: &[PathMember], callback: Box 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(()), } } diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index d900a4cd34..03fe42abe1 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -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( diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 72b800eb9c..973bb91e02 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -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 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 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)); }