diff --git a/Cargo.lock b/Cargo.lock index 696490da9f..5aa2a2c69f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,6 +98,15 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "byte-unit" +version = "4.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063197e6eb4b775b64160dedde7a0986bb2836cce140e9492e9e96f28e18bcd8" +dependencies = [ + "utf8-width", +] + [[package]] name = "cc" version = "1.0.70" @@ -119,10 +128,20 @@ dependencies = [ "libc", "num-integer", "num-traits", + "serde", "time", "winapi", ] +[[package]] +name = "chrono-humanize" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eddc119501d583fd930cb92144e605f44e0252c38dd89d9247fffa1993375cb" +dependencies = [ + "chrono", +] + [[package]] name = "core-foundation-sys" version = "0.8.2" @@ -497,6 +516,7 @@ dependencies = [ name = "nu-command" version = "0.1.0" dependencies = [ + "chrono", "glob", "nu-engine", "nu-json", @@ -550,6 +570,9 @@ dependencies = [ name = "nu-protocol" version = "0.1.0" dependencies = [ + "byte-unit", + "chrono", + "chrono-humanize", "miette", "serde", "thiserror", @@ -1099,6 +1122,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "utf8-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b" + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index c6bcd32f9b..f58ebd5795 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -15,4 +15,5 @@ nu-table = { path = "../nu-table" } # Potential dependencies for extras glob = "0.3.0" thiserror = "1.0.29" -sysinfo = "0.20.4" \ No newline at end of file +sysinfo = "0.20.4" +chrono = { version="0.4.19", features=["serde"] } \ No newline at end of file diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 77b9f1c602..5e3ab2e2b9 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use nu_engine::eval_expression; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EvaluationContext}; @@ -56,25 +57,39 @@ impl Command for Ls { let is_dir = metadata.is_dir(); let filesize = metadata.len(); + let mut cols = vec!["name".into(), "type".into(), "size".into()]; + + let mut vals = vec![ + Value::String { + val: path.to_string_lossy().to_string(), + span: call_span, + }, + if is_file { + Value::string("File", call_span) + } else if is_dir { + Value::string("Dir", call_span) + } else { + Value::Nothing { span: call_span } + }, + Value::Filesize { + val: filesize as i64, + span: call_span, + }, + ]; + + if let Ok(date) = metadata.modified() { + let utc: DateTime = date.into(); + + cols.push("modified".into()); + vals.push(Value::Date { + val: utc.into(), + span: call_span, + }); + } + Value::Record { - cols: vec!["name".into(), "type".into(), "size".into()], - vals: vec![ - Value::String { - val: path.to_string_lossy().to_string(), - span: call_span, - }, - if is_file { - Value::string("file", call_span) - } else if is_dir { - Value::string("dir", call_span) - } else { - Value::Nothing { span: call_span } - }, - Value::Filesize { - val: filesize, - span: call_span, - }, - ], + cols, + vals, span: call_span, } } diff --git a/crates/nu-command/src/system/ps.rs b/crates/nu-command/src/system/ps.rs index b51310a387..68f9409a89 100644 --- a/crates/nu-command/src/system/ps.rs +++ b/crates/nu-command/src/system/ps.rs @@ -86,13 +86,13 @@ fn run_ps(call: &Call) -> Result { cols.push("mem".into()); vals.push(Value::Filesize { - val: result.memory() * 1000, + val: result.memory() as i64 * 1000, span, }); cols.push("virtual".into()); vals.push(Value::Filesize { - val: result.virtual_memory() * 1000, + val: result.virtual_memory() as i64 * 1000, span, }); diff --git a/crates/nu-command/src/system/sys.rs b/crates/nu-command/src/system/sys.rs index c3bf8591dd..b0323ffdf6 100644 --- a/crates/nu-command/src/system/sys.rs +++ b/crates/nu-command/src/system/sys.rs @@ -112,13 +112,13 @@ pub fn disks(sys: &mut System, span: Span) -> Option { cols.push("total".into()); vals.push(Value::Filesize { - val: disk.total_space(), + val: disk.total_space() as i64, span, }); cols.push("free".into()); vals.push(Value::Filesize { - val: disk.available_space(), + val: disk.available_space() as i64, span, }); @@ -148,13 +148,13 @@ pub fn net(sys: &mut System, span: Span) -> Option { cols.push("sent".into()); vals.push(Value::Filesize { - val: data.total_transmitted(), + val: data.total_transmitted() as i64, span, }); cols.push("recv".into()); vals.push(Value::Filesize { - val: data.total_received(), + val: data.total_received() as i64, span, }); @@ -215,25 +215,25 @@ pub fn mem(sys: &mut System, span: Span) -> Option { cols.push("total".into()); vals.push(Value::Filesize { - val: total_mem * 1000, + val: total_mem as i64 * 1000, span, }); cols.push("free".into()); vals.push(Value::Filesize { - val: free_mem * 1000, + val: free_mem as i64 * 1000, span, }); cols.push("swap total".into()); vals.push(Value::Filesize { - val: total_swap * 1000, + val: total_swap as i64 * 1000, span, }); cols.push("swap free".into()); vals.push(Value::Filesize { - val: free_swap * 1000, + val: free_swap as i64 * 1000, span, }); @@ -276,7 +276,7 @@ pub fn host(sys: &mut System, span: Span) -> Option { } cols.push("uptime".into()); vals.push(Value::Duration { - val: 1000000000 * sys.uptime() as u64, + val: 1000000000 * sys.uptime() as i64, span, }); diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 5882ca2445..d59ff4b3e0 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,6 +1,6 @@ use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::EvaluationContext; -use nu_protocol::{Range, ShellError, Span, Type, Value}; +use nu_protocol::{Range, ShellError, Span, Type, Unit, Value}; pub fn eval_operator(op: &Expression) -> Result { match op { @@ -123,6 +123,10 @@ pub fn eval_expression( val: *f, span: expr.span, }), + Expr::ValueWithUnit(e, unit) => match eval_expression(context, e)? { + Value::Int { val, .. } => Ok(compute(val, unit.item, unit.span)), + _ => Err(ShellError::CantConvert("unit value".into(), e.span)), + }, Expr::Range(from, next, to, operator) => { let from = if let Some(f) = from { eval_expression(context, f)? @@ -291,3 +295,80 @@ pub fn eval_block( Ok(input) } + +pub fn compute(size: i64, unit: Unit, span: Span) -> Value { + match unit { + Unit::Byte => Value::Filesize { val: size, span }, + Unit::Kilobyte => Value::Filesize { + val: size * 1000, + span, + }, + Unit::Megabyte => Value::Filesize { + val: size * 1000 * 1000, + span, + }, + Unit::Gigabyte => Value::Filesize { + val: size * 1000 * 1000 * 1000, + span, + }, + Unit::Terabyte => Value::Filesize { + val: size * 1000 * 1000 * 1000 * 1000, + span, + }, + Unit::Petabyte => Value::Filesize { + val: size * 1000 * 1000 * 1000 * 1000 * 1000, + span, + }, + + Unit::Kibibyte => Value::Filesize { + val: size * 1024, + span, + }, + Unit::Mebibyte => Value::Filesize { + val: size * 1024 * 1024, + span, + }, + Unit::Gibibyte => Value::Filesize { + val: size * 1024 * 1024 * 1024, + span, + }, + Unit::Tebibyte => Value::Filesize { + val: size * 1024 * 1024 * 1024 * 1024, + span, + }, + Unit::Pebibyte => Value::Filesize { + val: size * 1024 * 1024 * 1024 * 1024 * 1024, + span, + }, + + Unit::Nanosecond => Value::Duration { val: size, span }, + Unit::Microsecond => Value::Duration { + val: size * 1000, + span, + }, + Unit::Millisecond => Value::Duration { + val: size * 1000 * 1000, + span, + }, + Unit::Second => Value::Duration { + val: size * 1000 * 1000 * 1000, + span, + }, + Unit::Minute => Value::Duration { + val: size * 1000 * 1000 * 1000 * 60, + span, + }, + Unit::Hour => Value::Duration { + val: size * 1000 * 1000 * 1000 * 60 * 60, + span, + }, + Unit::Day => Value::Duration { + val: size * 1000 * 1000 * 1000 * 60 * 60 * 24, + span, + }, + Unit::Week => Value::Duration { + val: size * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7, + span, + }, + } +} diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index c2d3ade9a4..90cfc5092d 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -81,6 +81,12 @@ pub fn flatten_expression( Expr::Float(_) => { vec![(expr.span, FlatShape::Float)] } + Expr::ValueWithUnit(x, unit) => { + let mut output = flatten_expression(working_set, x); + output.push((unit.span, FlatShape::String)); + + output + } Expr::CellPath(cell_path) => { let mut output = vec![]; for path_element in &cell_path.members { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 925cc76bb9..e1a4f62867 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -10,7 +10,7 @@ use nu_protocol::{ Operator, PathMember, Pipeline, RangeInclusion, RangeOperator, Statement, }, engine::StateWorkingSet, - span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId, + span, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId, }; use crate::parse_keywords::{ @@ -1351,6 +1351,193 @@ pub fn parse_filepath( } } +/// Parse a duration type, eg '10day' +pub fn parse_duration( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + fn parse_decimal_str_to_number(decimal: &str) -> Option { + let string_to_parse = format!("0.{}", decimal); + if let Ok(x) = string_to_parse.parse::() { + return Some((1_f64 / x) as i64); + } + None + } + + let bytes = working_set.get_span_contents(span); + let token = String::from_utf8_lossy(bytes).to_string(); + + let unit_groups = [ + (Unit::Nanosecond, "NS", None), + (Unit::Microsecond, "US", Some((Unit::Nanosecond, 1000))), + (Unit::Millisecond, "MS", Some((Unit::Microsecond, 1000))), + (Unit::Second, "SEC", Some((Unit::Millisecond, 1000))), + (Unit::Minute, "MIN", Some((Unit::Second, 60))), + (Unit::Hour, "HR", Some((Unit::Minute, 60))), + (Unit::Day, "DAY", Some((Unit::Minute, 1440))), + (Unit::Week, "WK", Some((Unit::Day, 7))), + ]; + if let Some(unit) = unit_groups + .iter() + .find(|&x| token.to_uppercase().ends_with(x.1)) + { + let mut lhs = token.clone(); + for _ in 0..unit.1.len() { + lhs.pop(); + } + + let input: Vec<&str> = lhs.split('.').collect(); + let (value, unit_to_use) = match &input[..] { + [number_str] => (number_str.parse::().ok(), unit.0), + [number_str, decimal_part_str] => match unit.2 { + Some(unit_to_convert_to) => match ( + number_str.parse::(), + parse_decimal_str_to_number(decimal_part_str), + ) { + (Ok(number), Some(decimal_part)) => ( + Some( + (number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part), + ), + unit_to_convert_to.0, + ), + _ => (None, unit.0), + }, + None => (None, unit.0), + }, + _ => (None, unit.0), + }; + + if let Some(x) = value { + let lhs_span = Span::new(span.start, span.start + lhs.len()); + let unit_span = Span::new(span.start + lhs.len(), span.end); + return ( + Expression { + expr: Expr::ValueWithUnit( + Box::new(Expression { + expr: Expr::Int(x), + span: lhs_span, + ty: Type::Number, + custom_completion: None, + }), + Spanned { + item: unit_to_use, + span: unit_span, + }, + ), + span, + ty: Type::Duration, + custom_completion: None, + }, + None, + ); + } + } + + ( + garbage(span), + Some(ParseError::Mismatch( + "duration".into(), + "non-duration unit".into(), + span, + )), + ) +} + +/// Parse a unit type, eg '10kb' +pub fn parse_filesize( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + fn parse_decimal_str_to_number(decimal: &str) -> Option { + let string_to_parse = format!("0.{}", decimal); + if let Ok(x) = string_to_parse.parse::() { + return Some((1_f64 / x) as i64); + } + None + } + + let bytes = working_set.get_span_contents(span); + let token = String::from_utf8_lossy(bytes).to_string(); + + let unit_groups = [ + (Unit::Kilobyte, "KB", Some((Unit::Byte, 1000))), + (Unit::Megabyte, "MB", Some((Unit::Kilobyte, 1000))), + (Unit::Gigabyte, "GB", Some((Unit::Megabyte, 1000))), + (Unit::Terabyte, "TB", Some((Unit::Gigabyte, 1000))), + (Unit::Petabyte, "PB", Some((Unit::Terabyte, 1000))), + (Unit::Kibibyte, "KIB", Some((Unit::Byte, 1024))), + (Unit::Mebibyte, "MIB", Some((Unit::Kibibyte, 1024))), + (Unit::Gibibyte, "GIB", Some((Unit::Mebibyte, 1024))), + (Unit::Tebibyte, "TIB", Some((Unit::Gibibyte, 1024))), + (Unit::Pebibyte, "PIB", Some((Unit::Tebibyte, 1024))), + (Unit::Byte, "B", None), + ]; + if let Some(unit) = unit_groups + .iter() + .find(|&x| token.to_uppercase().ends_with(x.1)) + { + let mut lhs = token.clone(); + for _ in 0..unit.1.len() { + lhs.pop(); + } + + let input: Vec<&str> = lhs.split('.').collect(); + let (value, unit_to_use) = match &input[..] { + [number_str] => (number_str.parse::().ok(), unit.0), + [number_str, decimal_part_str] => match unit.2 { + Some(unit_to_convert_to) => match ( + number_str.parse::(), + parse_decimal_str_to_number(decimal_part_str), + ) { + (Ok(number), Some(decimal_part)) => ( + Some( + (number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part), + ), + unit_to_convert_to.0, + ), + _ => (None, unit.0), + }, + None => (None, unit.0), + }, + _ => (None, unit.0), + }; + + if let Some(x) = value { + let lhs_span = Span::new(span.start, span.start + lhs.len()); + let unit_span = Span::new(span.start + lhs.len(), span.end); + return ( + Expression { + expr: Expr::ValueWithUnit( + Box::new(Expression { + expr: Expr::Int(x), + span: lhs_span, + ty: Type::Number, + custom_completion: None, + }), + Spanned { + item: unit_to_use, + span: unit_span, + }, + ), + span, + ty: Type::Filesize, + custom_completion: None, + }, + None, + ); + } + } + + ( + garbage(span), + Some(ParseError::Mismatch( + "filesize".into(), + "non-filesize unit".into(), + span, + )), + ) +} + pub fn parse_glob_pattern( working_set: &mut StateWorkingSet, span: Span, @@ -2381,6 +2568,8 @@ pub fn parse_value( } SyntaxShape::Number => parse_number(bytes, span), SyntaxShape::Int => parse_int(bytes, span), + SyntaxShape::Duration => parse_duration(working_set, span), + SyntaxShape::Filesize => parse_filesize(working_set, span), SyntaxShape::Range => parse_range(working_set, span), SyntaxShape::Filepath => parse_filepath(working_set, span), SyntaxShape::GlobPattern => parse_glob_pattern(working_set, span), diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index fa07f755da..03ce14c6e6 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -29,6 +29,9 @@ pub fn math_result_type( (Type::Int, Type::Float) => (Type::Float, None), (Type::Float, Type::Float) => (Type::Float, None), (Type::String, Type::String) => (Type::String, None), + (Type::Duration, Type::Duration) => (Type::Duration, None), + (Type::Filesize, Type::Filesize) => (Type::Filesize, None), + (Type::Unknown, _) => (Type::Unknown, None), (_, Type::Unknown) => (Type::Unknown, None), (Type::Int, _) => { @@ -64,6 +67,9 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None), (Type::Float, Type::Float) => (Type::Float, None), + (Type::Duration, Type::Duration) => (Type::Duration, None), + (Type::Filesize, Type::Filesize) => (Type::Filesize, None), + (Type::Unknown, _) => (Type::Unknown, None), (_, Type::Unknown) => (Type::Unknown, None), _ => { @@ -85,6 +91,7 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None), (Type::Float, Type::Float) => (Type::Float, None), + (Type::Unknown, _) => (Type::Unknown, None), (_, Type::Unknown) => (Type::Unknown, None), _ => { @@ -106,6 +113,7 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None), (Type::Float, Type::Float) => (Type::Float, None), + (Type::Unknown, _) => (Type::Unknown, None), (_, Type::Unknown) => (Type::Unknown, None), _ => { @@ -127,6 +135,9 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None), (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), _ => { @@ -148,6 +159,9 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None), (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), _ => { @@ -169,6 +183,9 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None), (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), _ => { @@ -190,6 +207,9 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None), (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), _ => { @@ -209,6 +229,9 @@ pub fn math_result_type( Operator::Equal => match (&lhs.ty, &rhs.ty) { (Type::Float, Type::Int) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (x, y) if x == y => (Type::Bool, None), (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), @@ -231,6 +254,9 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None), (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), _ => { diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index a4fbcd6bf8..c44b03cf08 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -9,3 +9,6 @@ edition = "2018" thiserror = "1.0.29" miette = "3.0.0" serde = {version = "1.0.130", features = ["derive"]} +chrono = { version="0.4.19", features=["serde"] } +chrono-humanize = "0.2.1" +byte-unit = "4.0.9" \ No newline at end of file diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 45955da946..4a9b42527d 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -1,5 +1,5 @@ use super::{Call, CellPath, Expression, FullCellPath, Operator, RangeOperator}; -use crate::{BlockId, Signature, Span, VarId}; +use crate::{BlockId, Signature, Span, Spanned, Unit, VarId}; #[derive(Debug, Clone)] pub enum Expr { @@ -23,6 +23,7 @@ pub enum Expr { List(Vec), Table(Vec, Vec>), Keyword(Vec, Span, Box), + ValueWithUnit(Box, Spanned), Filepath(String), GlobPattern(String), String(String), // FIXME: improve this in the future? diff --git a/crates/nu-protocol/src/span.rs b/crates/nu-protocol/src/span.rs index 1dd0b38982..744915783a 100644 --- a/crates/nu-protocol/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -1,7 +1,11 @@ use miette::SourceSpan; use serde::{Deserialize, Serialize}; -pub struct Spanned { +#[derive(Clone, Debug)] +pub struct Spanned +where + T: Clone + std::fmt::Debug, +{ pub item: T, pub span: Span, } diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index e3015b7c05..9c70210cfe 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -12,6 +12,7 @@ pub enum Type { Block, CellPath, Duration, + Date, Filesize, List(Box), Number, @@ -30,6 +31,7 @@ impl Display for Type { Type::Block => write!(f, "block"), Type::Bool => write!(f, "bool"), Type::CellPath => write!(f, "cell path"), + Type::Date => write!(f, "date"), Type::Duration => write!(f, "duration"), Type::Filesize => write!(f, "filesize"), Type::Float => write!(f, "float"), diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index eae64b8dc8..1e634964a4 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -1,11 +1,15 @@ mod range; mod row; mod stream; +mod unit; +use chrono::{DateTime, FixedOffset}; +use chrono_humanize::HumanTime; pub use range::*; pub use row::*; use serde::{Deserialize, Serialize}; pub use stream::*; +pub use unit::*; use std::fmt::Debug; @@ -26,11 +30,15 @@ pub enum Value { span: Span, }, Filesize { - val: u64, + val: i64, span: Span, }, Duration { - val: u64, + val: i64, + span: Span, + }, + Date { + val: DateTime, span: Span, }, Range { @@ -95,6 +103,7 @@ impl Value { Value::Float { span, .. } => *span, Value::Filesize { span, .. } => *span, Value::Duration { span, .. } => *span, + Value::Date { span, .. } => *span, Value::Range { span, .. } => *span, Value::String { span, .. } => *span, Value::Record { span, .. } => *span, @@ -115,6 +124,7 @@ impl Value { Value::Float { span, .. } => *span = new_span, Value::Filesize { span, .. } => *span = new_span, Value::Duration { span, .. } => *span = new_span, + Value::Date { span, .. } => *span = new_span, Value::Range { span, .. } => *span = new_span, Value::String { span, .. } => *span = new_span, Value::Record { span, .. } => *span = new_span, @@ -138,6 +148,7 @@ impl Value { Value::Float { .. } => Type::Float, Value::Filesize { .. } => Type::Filesize, Value::Duration { .. } => Type::Duration, + Value::Date { .. } => Type::Date, Value::Range { .. } => Type::Range, Value::String { .. } => Type::String, Value::Record { cols, vals, .. } => { @@ -159,8 +170,9 @@ impl Value { Value::Bool { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), - Value::Filesize { val, .. } => format!("{} bytes", val), - Value::Duration { val, .. } => format!("{} ns", val), + Value::Filesize { val, .. } => format_filesize(val), + Value::Duration { val, .. } => format_duration(val), + Value::Date { val, .. } => HumanTime::from(val).to_string(), Value::Range { val, .. } => { format!( "range: [{}]", @@ -202,6 +214,7 @@ impl Value { Value::Float { val, .. } => val.to_string(), Value::Filesize { val, .. } => format!("{} bytes", val), Value::Duration { val, .. } => format!("{} ns", val), + Value::Date { val, .. } => format!("{:?}", val), Value::Range { val, .. } => val .into_iter() .map(|x| x.into_string()) @@ -390,6 +403,18 @@ impl Value { val: lhs.to_string() + rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Duration { + val: *lhs + *rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Filesize { + val: *lhs + *rhs, + span, + }) + } _ => Err(ShellError::OperatorMismatch { op_span: op, @@ -420,6 +445,18 @@ impl Value { val: lhs - rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Duration { + val: *lhs - *rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Filesize { + val: *lhs - *rhs, + span, + }) + } _ => Err(ShellError::OperatorMismatch { op_span: op, @@ -541,6 +578,19 @@ impl Value { val: lhs < rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs < rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs < rhs, + span, + }) + } + _ => Err(ShellError::OperatorMismatch { op_span: op, lhs_ty: self.get_type(), @@ -570,6 +620,18 @@ impl Value { val: lhs <= rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs <= rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs <= rhs, + span, + }) + } _ => Err(ShellError::OperatorMismatch { op_span: op, lhs_ty: self.get_type(), @@ -599,6 +661,18 @@ impl Value { val: lhs > rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs > rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs > rhs, + span, + }) + } _ => Err(ShellError::OperatorMismatch { op_span: op, lhs_ty: self.get_type(), @@ -628,6 +702,18 @@ impl Value { val: lhs >= rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs >= rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs >= rhs, + span, + }) + } _ => Err(ShellError::OperatorMismatch { op_span: op, lhs_ty: self.get_type(), @@ -664,6 +750,18 @@ impl Value { val: lhs == rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs == rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs == rhs, + span, + }) + } (Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => Ok(Value::Bool { val: lhs == rhs, span, @@ -719,6 +817,18 @@ impl Value { val: lhs != rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs != rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs != rhs, + span, + }) + } (Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => Ok(Value::Bool { val: lhs != rhs, span, @@ -749,3 +859,69 @@ impl Value { } } } + +/// Format a duration in nanoseconds into a string +pub fn format_duration(duration: i64) -> String { + let (sign, duration) = if duration >= 0 { + (1, duration) + } else { + (-1, -duration) + }; + let (micros, nanos): (i64, i64) = (duration / 1000, duration % 1000); + let (millis, micros): (i64, i64) = (micros / 1000, micros % 1000); + let (secs, millis): (i64, i64) = (millis / 1000, millis % 1000); + let (mins, secs): (i64, i64) = (secs / 60, secs % 60); + let (hours, mins): (i64, i64) = (mins / 60, mins % 60); + let (days, hours): (i64, i64) = (hours / 24, hours % 24); + + let mut output_prep = vec![]; + + if days != 0 { + output_prep.push(format!("{}day", days)); + } + + if hours != 0 { + output_prep.push(format!("{}hr", hours)); + } + + if mins != 0 { + output_prep.push(format!("{}min", mins)); + } + // output 0sec for zero duration + if duration == 0 || secs != 0 { + output_prep.push(format!("{}sec", secs)); + } + + if millis != 0 { + output_prep.push(format!("{}ms", millis)); + } + + if micros != 0 { + output_prep.push(format!("{}us", micros)); + } + + if nanos != 0 { + output_prep.push(format!("{}ns", nanos)); + } + + format!( + "{}{}", + if sign == -1 { "-" } else { "" }, + output_prep.join(" ") + ) +} + +fn format_filesize(num_bytes: i64) -> String { + let byte = byte_unit::Byte::from_bytes(num_bytes as u128); + + if byte.get_bytes() == 0u128 { + return "—".to_string(); + } + + let byte = byte.get_appropriate_unit(false); + + match byte.get_unit() { + byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()), + _ => byte.format(1), + } +} diff --git a/crates/nu-protocol/src/value/unit.rs b/crates/nu-protocol/src/value/unit.rs new file mode 100644 index 0000000000..27fd893014 --- /dev/null +++ b/crates/nu-protocol/src/value/unit.rs @@ -0,0 +1,27 @@ +#[derive(Debug, Clone, Copy)] +pub enum Unit { + // Filesize units: metric + Byte, + Kilobyte, + Megabyte, + Gigabyte, + Terabyte, + Petabyte, + + // Filesize units: ISO/IEC 80000 + Kibibyte, + Mebibyte, + Gibibyte, + Tebibyte, + Pebibyte, + + // Duration units + Nanosecond, + Microsecond, + Millisecond, + Second, + Minute, + Hour, + Day, + Week, +}