diff --git a/crates/nu-command/src/commands/conversions/into/filesize.rs b/crates/nu-command/src/commands/conversions/into/filesize.rs new file mode 100644 index 0000000000..7a3a648341 --- /dev/null +++ b/crates/nu-command/src/commands/conversions/into/filesize.rs @@ -0,0 +1,182 @@ +use std::convert::TryInto; + +use crate::prelude::*; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value}; +use num_bigint::ToBigInt; + +pub struct SubCommand; + +impl WholeStreamCommand for SubCommand { + fn name(&self) -> &str { + "into filesize" + } + + fn signature(&self) -> Signature { + Signature::build("into filesize").rest( + "rest", + SyntaxShape::ColumnPath, + "column paths to convert to filesize (for table input)", + ) + } + + fn usage(&self) -> &str { + "Convert value to filesize" + } + + fn run(&self, args: CommandArgs) -> Result { + into_filesize(args) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Convert string to filesize in table", + example: "echo [[bytes]; ['5'] [3.2] [4] [2kb]] | into filesize bytes", + result: Some(vec![ + UntaggedValue::row(indexmap! { + "bytes".to_string() => UntaggedValue::filesize(5).into(), + }) + .into(), + UntaggedValue::row(indexmap! { + "bytes".to_string() => UntaggedValue::filesize(3).into(), + }) + .into(), + UntaggedValue::row(indexmap! { + "bytes".to_string() => UntaggedValue::filesize(4).into(), + }) + .into(), + UntaggedValue::row(indexmap! { + "bytes".to_string() => UntaggedValue::filesize(2000).into(), + }) + .into(), + ]), + }, + Example { + description: "Convert string to filesize", + example: "echo '2' | into filesize", + result: Some(vec![UntaggedValue::filesize(2).into()]), + }, + Example { + description: "Convert decimal to filesize", + example: "echo 8.3 | into filesize", + result: Some(vec![UntaggedValue::filesize(8).into()]), + }, + Example { + description: "Convert int to filesize", + example: "echo 5 | into filesize", + result: Some(vec![UntaggedValue::filesize(5).into()]), + }, + Example { + description: "Convert file size to filesize", + example: "echo 4KB | into filesize", + result: Some(vec![UntaggedValue::filesize(4000).into()]), + }, + ] + } +} + +fn into_filesize(args: CommandArgs) -> Result { + let column_paths: Vec = args.rest(0)?; + + Ok(args + .input + .map(move |v| { + 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) + } + }) + .into_input_stream()) +} + +pub fn action(input: &Value, tag: impl Into) -> Result { + let tag = tag.into(); + match &input.value { + UntaggedValue::Primitive(prim) => Ok(UntaggedValue::filesize(match prim { + Primitive::String(a_string) => match int_from_string(a_string.trim(), &tag) { + Ok(n) => n, + Err(e) => { + return Err(e); + } + }, + Primitive::Decimal(dec) => match dec.to_bigint() { + Some(n) => match n.to_u64() { + Some(i) => i, + None => { + return Err(ShellError::unimplemented( + "failed to convert decimal to filesize", + )); + } + }, + None => { + return Err(ShellError::unimplemented( + "failed to convert decimal to filesize", + )); + } + }, + Primitive::Int(n_ref) => (*n_ref).try_into().map_err(|_| { + ShellError::unimplemented("cannot convert negative integer to filesize") + })?, + Primitive::Filesize(a_filesize) => *a_filesize, + _ => { + return Err(ShellError::unimplemented( + "'into filesize' for non-numeric primitives", + )) + } + }) + .into_value(&tag)), + UntaggedValue::Row(_) => Err(ShellError::labeled_error( + "specify column name to use, with 'into filesize COLUMN'", + "found table", + tag, + )), + _ => Err(ShellError::unimplemented( + "'into filesize' for unsupported type", + )), + } +} + +fn int_from_string(a_string: &str, tag: &Tag) -> Result { + match a_string.parse::() { + Ok(n) => Ok(n), + Err(_) => match a_string.parse::() { + Ok(f) => match f.to_u64() { + Some(i) => Ok(i), + None => Err(ShellError::labeled_error( + "Could not convert string value to filesize", + "original value", + tag.clone(), + )), + }, + Err(_) => Err(ShellError::labeled_error( + "Could not convert string value to filesize", + "original value", + tag.clone(), + )), + }, + } +} + +#[cfg(test)] +mod tests { + use super::ShellError; + use super::SubCommand; + + #[test] + fn examples_work_as_expected() -> Result<(), ShellError> { + use crate::examples::test as test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/commands/conversions/into/mod.rs b/crates/nu-command/src/commands/conversions/into/mod.rs index efd01476cf..949c36a7b5 100644 --- a/crates/nu-command/src/commands/conversions/into/mod.rs +++ b/crates/nu-command/src/commands/conversions/into/mod.rs @@ -1,9 +1,11 @@ mod binary; mod command; mod filepath; +mod filesize; mod int; pub mod string; +pub use self::filesize::SubCommand as IntoFilesize; pub use binary::SubCommand as IntoBinary; pub use command::Command as Into; pub use filepath::SubCommand as IntoFilepath; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index e86c404b07..0aa0995b4e 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -137,6 +137,7 @@ pub fn create_default_context(interactive: bool) -> Result