diff --git a/crates/nu-command/src/conversions/into/binary.rs b/crates/nu-command/src/conversions/into/binary.rs index 1cd2eb7b4..b0eecf013 100644 --- a/crates/nu-command/src/conversions/into/binary.rs +++ b/crates/nu-command/src/conversions/into/binary.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,11 +28,11 @@ impl Command for SubCommand { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { - into_binary(engine_state, call, input) + into_binary(engine_state, stack, call, input) } fn examples(&self) -> Vec { @@ -87,27 +88,29 @@ impl Command for SubCommand { fn into_binary( 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 in cell_path support - // if column_paths.is_empty() { - // action(v, head) - // } else { - // let mut ret = v; - // for path in &column_paths { - // ret = - // ret.swap_data_by_cell_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(), ) @@ -129,19 +132,19 @@ fn float_to_endian(n: f64) -> Vec { } } -pub fn action(input: Value, span: Span) -> Value { +pub fn action(input: &Value, span: Span) -> Value { match input { - Value::Binary { .. } => input, + Value::Binary { .. } => input.clone(), Value::Int { val, .. } => Value::Binary { - val: int_to_endian(val), + val: int_to_endian(*val), span, }, Value::Float { val, .. } => Value::Binary { - val: float_to_endian(val), + val: float_to_endian(*val), span, }, Value::Filesize { val, .. } => Value::Binary { - val: int_to_endian(val), + val: int_to_endian(*val), span, }, Value::String { val, .. } => Value::Binary { @@ -149,7 +152,7 @@ pub fn action(input: Value, span: Span) -> Value { span, }, Value::Bool { val, .. } => Value::Binary { - val: int_to_endian(if val { 1i64 } else { 0 }), + val: int_to_endian(if *val { 1i64 } else { 0 }), span, }, Value::Date { val, .. } => Value::Binary { diff --git a/crates/nu-command/src/conversions/into/filesize.rs b/crates/nu-command/src/conversions/into/filesize.rs index 124914c73..d17ceec8f 100644 --- a/crates/nu-command/src/conversions/into/filesize.rs +++ b/crates/nu-command/src/conversions/into/filesize.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,56 +28,20 @@ impl Command for SubCommand { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { - into_filesize(engine_state, call, input) + into_filesize(engine_state, stack, call, input) } fn examples(&self) -> Vec { vec![ - // Example { - // description: "Convert string to filesize in table", - // example: "[[bytes]; ['5'] [3.2] [4] [2kb]] | into filesize bytes", - // result: Some(Value::List { - // vals: vec![ - // Value::Record { - // cols: vec!["bytes".to_string()], - // vals: vec![Value::Filesize { - // val: 5, - // span: Span::unknown(), - // }], - // span: Span::unknown(), - // }, - // Value::Record { - // cols: vec!["bytes".to_string()], - // vals: vec![Value::Filesize { - // val: 3, - // span: Span::unknown(), - // }], - // span: Span::unknown(), - // }, - // Value::Record { - // cols: vec!["bytes".to_string()], - // vals: vec![Value::Filesize { - // val: 4, - // span: Span::unknown(), - // }], - // span: Span::unknown(), - // }, - // Value::Record { - // cols: vec!["bytes".to_string()], - // vals: vec![Value::Filesize { - // val: 2000, - // span: Span::unknown(), - // }], - // span: Span::unknown(), - // }, - // ], - // span: Span::unknown(), - // }), - // }, + Example { + description: "Convert string to filesize in table", + example: "[[bytes]; ['5'] [3.2] [4] [2kb]] | into filesize bytes", + result: None, + }, Example { description: "Convert string to filesize", example: "'2' | into filesize", @@ -115,44 +80,43 @@ impl Command for SubCommand { fn into_filesize( engine_state: &EngineState, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { let head = call.head; - // let call_paths: Vec = args.rest(0)?; + let column_paths: Vec = call.rest(engine_state, stack, 0)?; input.map( move |v| { - action(v, head) + 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 }; + } + } - // 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())), - // )?; - // } - - // 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::Filesize { .. } => input, - Value::Int { val, .. } => Value::Filesize { val, span }, + Value::Filesize { .. } => input.clone(), + Value::Int { val, .. } => Value::Filesize { val: *val, span }, Value::Float { val, .. } => Value::Filesize { - 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::Filesize { val, span }, Err(error) => Value::Error { error }, }, diff --git a/crates/nu-command/src/conversions/into/int.rs b/crates/nu-command/src/conversions/into/int.rs index e6bceb915..5167bcfbb 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-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index 31e9fa9c0..4f094926f 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -1,6 +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, }; @@ -18,11 +18,11 @@ impl Command for SubCommand { fn signature(&self) -> Signature { Signature::build("into string") // FIXME - need to support column paths - // .rest( - // "rest", - // SyntaxShape::ColumnPaths(), - // "column paths to convert to string (for table input)", - // ) + .rest( + "rest", + SyntaxShape::CellPath, + "column paths to convert to string (for table input)", + ) .named( "decimals", SyntaxShape::Int, @@ -135,6 +135,7 @@ fn string_helper( let decimals = call.has_flag("decimals"); let head = call.head; let decimals_value: Option = call.get_flag(engine_state, stack, "decimals")?; + let column_paths: Vec = call.rest(engine_state, stack, 0)?; if decimals && decimals_value.is_some() && decimals_value.unwrap().is_negative() { return Err(ShellError::UnsupportedInput( @@ -144,13 +145,30 @@ fn string_helper( } input.map( - move |v| action(v, head, decimals, decimals_value, false), + move |v| { + if column_paths.is_empty() { + action(&v, head, decimals, decimals_value, false) + } 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, decimals, decimals_value, false)), + ); + if let Err(error) = r { + return Value::Error { error }; + } + } + + ret + } + }, engine_state.ctrlc.clone(), ) } pub fn action( - input: Value, + input: &Value, span: Span, decimals: bool, digits: Option, @@ -159,7 +177,7 @@ pub fn action( match input { Value::Int { val, .. } => { let res = if group_digits { - format_int(val) // int.to_formatted_string(*locale) + format_int(*val) // int.to_formatted_string(*locale) } else { val.to_string() }; @@ -188,12 +206,13 @@ pub fn action( val: val.format("%c").to_string(), span, }, - Value::String { val, .. } => Value::String { val, span }, + Value::String { val, .. } => Value::String { + val: val.to_string(), + span, + }, - // FIXME - we do not have a FilePath type anymore. Do we need to support this? - // Value::FilePath(a_filepath) => a_filepath.as_path().display().to_string(), Value::Filesize { val: _, .. } => Value::String { - val: input.into_string(), + val: input.clone().into_string(), span, }, Value::Nothing { .. } => Value::String { diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index 182c25309..480a91bb5 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 257985645..db733671d 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 84ff1dd51..6aa3688b0 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 c0327b39b..958c1e68c 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 a9042eb06..cfa205bfc 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", + ) +}