diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index f18c2caefa..e3c5d2d478 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -180,6 +180,7 @@ pub fn create_default_context(interactive: bool) -> Result, +} + +#[async_trait] +impl WholeStreamCommand for FileSize { + fn name(&self) -> &str { + "format filesize" + } + + fn signature(&self) -> Signature { + Signature::build("format filesize") + .required( + "field", + SyntaxShape::ColumnPath, + "the name of the column to update", + ) + .required( + "format value", + SyntaxShape::String, + "the format into which convert the filesizes", + ) + } + + fn usage(&self) -> &str { + "Converts a column of filesizes to some specified format" + } + + async fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + filesize(args, registry).await + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Convert the size row to KB", + example: "ls | format filesize size KB", + result: None, + }, + Example { + description: "Convert the apparent row to B", + example: "du | format filesize apparent B", + result: None, + }, + ] + } +} + +async fn process_row( + input: Value, + format: Tagged, + field: Arc, +) -> Result { + Ok({ + let replace_for = get_data_by_column_path(&input, &field, move |_, _, error| error); + match replace_for { + Ok(s) => match convert_bytes_to_string_using_format(s, format) { + Ok(b) => OutputStream::one(ReturnSuccess::value( + input.replace_data_at_column_path(&field, b).expect("Given that the existance check was already done, this souldn't trigger never"), + )), + Err(e) => OutputStream::one(Err(e)), + }, + Err(e) => OutputStream::one(Err(e)), + } + }) +} + +async fn filesize( + raw_args: CommandArgs, + registry: &CommandRegistry, +) -> Result { + let registry = registry.clone(); + let (Arguments { field, format }, input) = raw_args.process(®istry).await?; + let field = Arc::new(field); + + Ok(input + .then(move |input| { + let format = format.clone(); + let field = field.clone(); + + async { + match process_row(input, format, field).await { + Ok(s) => s, + Err(e) => OutputStream::one(Err(e)), + } + } + }) + .flatten() + .to_output_stream()) +} + +fn convert_bytes_to_string_using_format( + bytes: Value, + format: Tagged, +) -> Result { + match bytes.value { + Primitive(Filesize(b)) => { + let byte = byte_unit::Byte::from_bytes(b as u128); + let value = match format.item().to_lowercase().as_str() { + "b" => Ok(UntaggedValue::string(b.to_formatted_string(&Locale::en))), + "kb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::KB).to_string(), + )), + "kib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::KiB).to_string(), + )), + "mb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::MB).to_string(), + )), + "mib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::MiB).to_string(), + )), + "gb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::GB).to_string(), + )), + "gib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::GiB).to_string(), + )), + "tb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::TB).to_string(), + )), + "tib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::TiB).to_string(), + )), + "pb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::PB).to_string(), + )), + "pib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::PiB).to_string(), + )), + "eb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::EB).to_string(), + )), + "eib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::EiB).to_string(), + )), + "zb" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::ZB).to_string(), + )), + "zib" => Ok(UntaggedValue::string( + byte.get_adjusted_unit(byte_unit::ByteUnit::ZiB).to_string(), + )), + _ => Err(ShellError::labeled_error( + format!("Invalid format code: {:}", format.item()), + "invalid format", + format.tag(), + )), + }; + match value { + Ok(b) => Ok(Value { value: b, ..bytes }), + Err(e) => Err(e), + } + } + _ => Err(ShellError::labeled_error( + "the data in this row is not of the type filesize", + "invalid row type", + bytes.tag(), + )), + } +} + +#[cfg(test)] +mod tests { + use super::FileSize; + use super::ShellError; + + #[test] + fn examples_work_as_expected() -> Result<(), ShellError> { + use crate::examples::test as test_examples; + + Ok(test_examples(FileSize {})?) + } +} diff --git a/crates/nu-cli/src/commands/format/mod.rs b/crates/nu-cli/src/commands/format/mod.rs new file mode 100644 index 0000000000..cd8267069c --- /dev/null +++ b/crates/nu-cli/src/commands/format/mod.rs @@ -0,0 +1,5 @@ +pub mod command; +pub mod format_filesize; + +pub use command::Format; +pub use format_filesize::FileSize; diff --git a/crates/nu-cli/tests/commands/format.rs b/crates/nu-cli/tests/commands/format.rs index d827781fd8..eee38afce2 100644 --- a/crates/nu-cli/tests/commands/format.rs +++ b/crates/nu-cli/tests/commands/format.rs @@ -1,3 +1,5 @@ +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::playground::Playground; use nu_test_support::{nu, pipeline}; #[test] @@ -42,3 +44,26 @@ fn can_use_variables() { assert_eq!(actual.out, "nu is a new type of shell"); } + +#[test] +fn format_filesize_works() { + Playground::setup("format_filesize_test_1", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("andres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | format filesize size KB + | get size + | first + "# + )); + + assert_eq!(actual.out, "0.01 KB"); + }) +}