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 <saeed.gnu@gmail.com>
This commit is contained in:
Eli Flanagan 2021-04-09 10:17:39 -04:00 committed by GitHub
parent b791d1ab0d
commit 556596bce8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 133 additions and 37 deletions

View File

@ -1,28 +1,33 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; 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; pub struct IntoInt;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct IntoIntArgs { pub struct IntoIntArgs {
pub rest: Vec<Value>, pub rest: Vec<ColumnPath>,
} }
impl WholeStreamCommand for IntoInt { impl WholeStreamCommand for IntoInt {
fn name(&self) -> &str { fn name(&self) -> &str {
"into-int" "into int"
} }
fn signature(&self) -> Signature { 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 { fn usage(&self) -> &str {
"Convert value to integer." "Convert value to integer"
} }
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
@ -32,45 +37,136 @@ impl WholeStreamCommand for IntoInt {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
description: "Convert filesize to integer", description: "Convert string to integer in table",
example: "into-int 1kb | each { = $it / 1000 }", example: "echo [[num]; ['-5'] [4] [1.5]] | into int num",
result: Some(vec![UntaggedValue::int(1).into()]), 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 { Example {
description: "Convert filesize to integer", description: "Convert string to integer",
example: "into-int 1kib | each { = $it / 1024 }", example: "echo '2' | into int",
result: Some(vec![UntaggedValue::int(1).into()]), 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<OutputStream, ShellError> { fn into_int(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (args, _): (IntoIntArgs, _) = args.process()?; let (IntoIntArgs { rest: column_paths }, input) = args.process()?;
let stream = args.rest.into_iter().map(|i| match i { Ok(input
Value { .map(move |v| {
value: UntaggedValue::Primitive(primitive_val), 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<Tag>) -> Result<Value, ShellError> {
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, tag,
} => match primitive_val { )),
Primitive::Filesize(size) => OutputStream::one(Ok(ReturnSuccess::Value(Value { _ => Err(ShellError::unimplemented("'int' for unsupported type")),
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))),
});
Ok(stream.flatten().to_output_stream()) fn int_from_string(a_string: &str, tag: &Tag) -> Result<BigInt, ShellError> {
match a_string.parse::<BigInt>() {
Ok(n) => Ok(n),
Err(_) => match a_string.parse::<f64>() {
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)] #[cfg(test)]

View File

@ -5,7 +5,7 @@ fn into_int_filesize() {
let actual = nu!( let actual = nu!(
cwd: ".", pipeline( cwd: ".", pipeline(
r#" 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!( let actual = nu!(
cwd: ".", pipeline( cwd: ".", pipeline(
r#" 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!( let actual = nu!(
cwd: ".", pipeline( cwd: ".", pipeline(
r#" r#"
into-int 1024 | each {= $it / 1024 } echo 1024 | into int | each {= $it / 1024 }
"# "#
)); ));