diff --git a/crates/nu-command/src/conversions/into/int.rs b/crates/nu-command/src/conversions/into/int.rs index e6bceb915c..5167bcfbb0 100644 --- a/crates/nu-command/src/conversions/into/int.rs +++ b/crates/nu-command/src/conversions/into/int.rs @@ -1,5 +1,6 @@ +use nu_engine::CallExt; use nu_protocol::{ - ast::Call, + ast::{Call, CellPath}, engine::{Command, EngineState, Stack}, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; @@ -27,33 +28,20 @@ impl Command for SubCommand { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { - into_int(engine_state, call, input) + into_int(engine_state, stack, call, input) } fn examples(&self) -> Vec { vec![ - // Example { - // description: "Convert string to integer in table", - // example: "echo [[num]; ['-5'] [4] [1.5]] | into int num", - // result: Some(vec![ - // UntaggedValue::row(indexmap! { - // "num".to_string() => UntaggedValue::int(-5).into(), - // }) - // .into(), - // UntaggedValue::row(indexmap! { - // "num".to_string() => UntaggedValue::int(4).into(), - // }) - // .into(), - // UntaggedValue::row(indexmap! { - // "num".to_string() => UntaggedValue::int(1).into(), - // }) - // .into(), - // ]), - // }, + Example { + description: "Convert string to integer in table", + example: "echo [[num]; ['-5'] [4] [1.5]] | into int num", + result: None, + }, Example { description: "Convert string to integer", example: "'2' | into int", @@ -91,46 +79,48 @@ impl Command for SubCommand { fn into_int( engine_state: &EngineState, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { let head = call.head; - // let column_paths: Vec = call.rest(context, 0)?; + let column_paths: Vec = call.rest(engine_state, stack, 0)?; input.map( move |v| { - action(v, head) - // FIXME: Add back cell_path support - // if column_paths.is_empty() { - // action(&v, v.tag()) - // } else { - // let mut ret = v; - // for path in &column_paths { - // ret = ret - // .swap_data_by_column_path(path, Box::new(move |old| action(old, old.tag())))?; - // } + if column_paths.is_empty() { + action(&v, head) + } else { + let mut ret = v; + for path in &column_paths { + let r = + ret.update_cell_path(&path.members, Box::new(move |old| action(old, head))); + if let Err(error) = r { + return Value::Error { error }; + } + } - // Ok(ret) - // } + ret + } }, engine_state.ctrlc.clone(), ) } -pub fn action(input: Value, span: Span) -> Value { +pub fn action(input: &Value, span: Span) -> Value { match input { - Value::Int { .. } => input, - Value::Filesize { val, .. } => Value::Int { val, span }, + Value::Int { .. } => input.clone(), + Value::Filesize { val, .. } => Value::Int { val: *val, span }, Value::Float { val, .. } => Value::Int { - val: val as i64, + val: *val as i64, span, }, - Value::String { val, .. } => match int_from_string(&val, span) { + Value::String { val, .. } => match int_from_string(val, span) { Ok(val) => Value::Int { val, span }, Err(error) => Value::Error { error }, }, Value::Bool { val, .. } => { - if val { + if *val { Value::Int { val: 1, span } } else { Value::Int { val: 0, span } diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index 182c253092..480a91bb58 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -70,6 +70,22 @@ impl PipelineData { } } + pub fn update_cell_path( + &mut self, + cell_path: &[PathMember], + callback: Box Value>, + ) -> Result<(), ShellError> { + match self { + // FIXME: there are probably better ways of doing this + PipelineData::Stream(stream) => Value::List { + vals: stream.collect(), + span: Span::unknown(), + } + .update_cell_path(cell_path, callback), + PipelineData::Value(v) => v.update_cell_path(cell_path, callback), + } + } + /// Simplified mapper to help with simple values also. For full iterator support use `.into_iter()` instead pub fn map( self, diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 2579856452..db733671de 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -111,6 +111,13 @@ pub enum ShellError { #[label = "value originates here"] Span, ), + #[error("Not a list value")] + #[diagnostic(code(nu::shell::not_a_list), url(docsrs))] + NotAList( + #[label = "value not a list"] Span, + #[label = "value originates here"] Span, + ), + #[error("External command")] #[diagnostic(code(nu::shell::external_command), url(docsrs))] ExternalCommand(String, #[label("{0}")] Span), diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 84ff1dd51d..6aa3688b0b 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -312,6 +312,78 @@ impl Value { Ok(current) } + /// Follow a given column path into the value: for example accessing nth 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.replace_data_at_cell_path(cell_path, new_val), + } + } + + pub fn replace_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, .. } => { + for col in cols.iter().zip(vals) { + if col.0 == col_name { + col.1.replace_data_at_cell_path( + &cell_path[1..], + new_val.clone(), + )? + } + } + } + v => return Err(ShellError::CantFindColumn(*span, v.span()?)), + } + } + } + Value::Record { cols, vals, .. } => { + for col in cols.iter().zip(vals) { + if col.0 == col_name { + col.1 + .replace_data_at_cell_path(&cell_path[1..], new_val.clone())? + } + } + } + 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.replace_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 is_true(&self) -> bool { matches!(self, Value::Bool { val: true, .. }) } diff --git a/crates/nu_plugin_inc/src/inc.rs b/crates/nu_plugin_inc/src/inc.rs index c0327b39be..958c1e68c6 100644 --- a/crates/nu_plugin_inc/src/inc.rs +++ b/crates/nu_plugin_inc/src/inc.rs @@ -86,7 +86,7 @@ impl Inc { match value { Value::Int { val, span } => Ok(Value::Int { val: val + 1, - span: span.clone(), + span: *span, }), Value::String { val, .. } => Ok(self.apply(val)), _ => Err(PluginError::RunTimeError("incrementable value".to_string())), diff --git a/src/tests.rs b/src/tests.rs index a9042eb06d..cfa205bfc6 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -831,3 +831,11 @@ fn shorthand_env_3() -> TestResult { fn shorthand_env_4() -> TestResult { fail_test(r#"FOO=BAZ FOO= $nu.env.FOO"#, "cannot find column") } + +#[test] +fn update_cell_path_1() -> TestResult { + run_test( + r#"[[name, size]; [a, 1.1]] | into int size | get size.0"#, + "1", + ) +}