From 556596bce82fb643907824cbe99447dc2637ac40 Mon Sep 17 00:00:00 2001 From: Eli Flanagan Date: Fri, 9 Apr 2021 10:17:39 -0400 Subject: [PATCH] add "into int" behavior (#3279) * add 'int' command * create `into int` behavior - forcibly overwrote prior implementation ```sh git mv -f crates/nu-command/src/commands/int_.rs crates/nu-command/src/commands/into_int.rs ``` - picked up prior work, polished and renamed Co-authored-by: Saeed Rasooli --- crates/nu-command/src/commands/into_int.rs | 164 +++++++++++++++---- crates/nu-command/tests/commands/into_int.rs | 6 +- 2 files changed, 133 insertions(+), 37 deletions(-) diff --git a/crates/nu-command/src/commands/into_int.rs b/crates/nu-command/src/commands/into_int.rs index 5e88f46445..b51cf0c330 100644 --- a/crates/nu-command/src/commands/into_int.rs +++ b/crates/nu-command/src/commands/into_int.rs @@ -1,28 +1,33 @@ use crate::prelude::*; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; -use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; +use nu_protocol::{ + ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value, +}; -use num_bigint::ToBigInt; +use num_bigint::{BigInt, ToBigInt}; pub struct IntoInt; #[derive(Deserialize)] pub struct IntoIntArgs { - pub rest: Vec, + pub rest: Vec, } impl WholeStreamCommand for IntoInt { fn name(&self) -> &str { - "into-int" + "into int" } fn signature(&self) -> Signature { - Signature::build("into-int").rest(SyntaxShape::Any, "the values to into-int") + Signature::build("into int").rest( + SyntaxShape::ColumnPath, + "column paths to convert to int (for table input)", + ) } fn usage(&self) -> &str { - "Convert value to integer." + "Convert value to integer" } fn run(&self, args: CommandArgs) -> Result { @@ -32,45 +37,136 @@ impl WholeStreamCommand for IntoInt { fn examples(&self) -> Vec { vec![ Example { - description: "Convert filesize to integer", - example: "into-int 1kb | each { = $it / 1000 }", - result: Some(vec![UntaggedValue::int(1).into()]), + 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 filesize to integer", - example: "into-int 1kib | each { = $it / 1024 }", - result: Some(vec![UntaggedValue::int(1).into()]), + description: "Convert string to integer", + example: "echo '2' | into int", + result: Some(vec![UntaggedValue::int(2).into()]), + }, + Example { + description: "Convert decimal to integer", + example: "echo 5.9 | into int", + result: Some(vec![UntaggedValue::int(5).into()]), + }, + Example { + description: "Convert decimal string to integer", + example: "echo '5.9' | into int", + result: Some(vec![UntaggedValue::int(5).into()]), + }, + Example { + description: "Convert file size to integer", + example: "echo 4KB | into int", + result: Some(vec![UntaggedValue::int(4000).into()]), + }, + Example { + description: "Convert bool to integer", + example: "echo $false $true | into int", + result: Some(vec![ + UntaggedValue::int(0).into(), + UntaggedValue::int(1).into(), + ]), }, ] } } fn into_int(args: CommandArgs) -> Result { - let (args, _): (IntoIntArgs, _) = args.process()?; + let (IntoIntArgs { rest: column_paths }, input) = args.process()?; - let stream = args.rest.into_iter().map(|i| match i { - Value { - value: UntaggedValue::Primitive(primitive_val), + Ok(input + .map(move |v| { + if column_paths.is_empty() { + ReturnSuccess::value(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())), + )?; + } + + ReturnSuccess::value(ret) + } + }) + .to_output_stream()) +} + +pub fn action(input: &Value, tag: impl Into) -> Result { + let tag = tag.into(); + match &input.value { + UntaggedValue::Primitive(prim) => Ok(UntaggedValue::int(match prim { + Primitive::String(a_string) => match int_from_string(a_string, &tag) { + Ok(n) => n, + Err(e) => { + return Err(e); + } + }, + Primitive::Decimal(dec) => match dec.to_bigint() { + Some(n) => n, + None => { + return Err(ShellError::unimplemented( + "failed to convert decimal to int", + )); + } + }, + Primitive::Int(n_ref) => n_ref.to_bigint().expect("unexpected error"), + Primitive::Boolean(a_bool) => match a_bool { + false => 0.to_bigint().expect("unexpected error"), + true => 1.to_bigint().expect("unexpected error"), + }, + Primitive::Filesize(a_filesize) => a_filesize + .to_bigint() + .expect("Conversion should never fail."), + _ => { + return Err(ShellError::unimplemented( + "'int' for non-numeric primitives", + )) + } + }) + .into_value(&tag)), + UntaggedValue::Row(_) => Err(ShellError::labeled_error( + "specify column name to use, with 'int COLUMN'", + "found table", tag, - } => match primitive_val { - Primitive::Filesize(size) => OutputStream::one(Ok(ReturnSuccess::Value(Value { - value: UntaggedValue::int(size.to_bigint().expect("Conversion should never fail.")), - tag, - }))), - Primitive::Int(_) => OutputStream::one(Ok(ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(primitive_val), - tag, - }))), - _ => OutputStream::one(Err(ShellError::labeled_error( - "Could not convert int value", - "original value", - tag, - ))), - }, - _ => OutputStream::one(Ok(ReturnSuccess::Value(i))), - }); + )), + _ => Err(ShellError::unimplemented("'int' for unsupported type")), + } +} - Ok(stream.flatten().to_output_stream()) +fn int_from_string(a_string: &str, tag: &Tag) -> Result { + match a_string.parse::() { + Ok(n) => Ok(n), + Err(_) => match a_string.parse::() { + Ok(res_float) => match res_float.to_bigint() { + Some(n) => Ok(n), + None => Err(ShellError::unimplemented( + "failed to convert decimal to int", + )), + }, + Err(_) => Err(ShellError::labeled_error( + "Could not convert string value to int", + "original value", + tag.clone(), + )), + }, + } } #[cfg(test)] diff --git a/crates/nu-command/tests/commands/into_int.rs b/crates/nu-command/tests/commands/into_int.rs index 6f7c755d89..82ae23bc9c 100644 --- a/crates/nu-command/tests/commands/into_int.rs +++ b/crates/nu-command/tests/commands/into_int.rs @@ -5,7 +5,7 @@ fn into_int_filesize() { let actual = nu!( cwd: ".", pipeline( r#" - into-int 1kb | each {= $it / 1000 } + echo 1kb | into int | each {= $it / 1000 } "# )); @@ -17,7 +17,7 @@ fn into_int_filesize2() { let actual = nu!( cwd: ".", pipeline( r#" - into-int 1kib | each {= $it / 1024 } + echo 1kib | into int | each {= $it / 1024 } "# )); @@ -29,7 +29,7 @@ fn into_int_int() { let actual = nu!( cwd: ".", pipeline( r#" - into-int 1024 | each {= $it / 1024 } + echo 1024 | into int | each {= $it / 1024 } "# ));