Merge pull request #93 from nushell/units

Add unit parsing and eval support
This commit is contained in:
JT 2021-10-05 15:32:14 +13:00 committed by GitHub
commit 14426433aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 598 additions and 38 deletions

29
Cargo.lock generated
View File

@ -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"

View File

@ -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"
sysinfo = "0.20.4"
chrono = { version="0.4.19", features=["serde"] }

View File

@ -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<Utc> = 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,
}
}

View File

@ -86,13 +86,13 @@ fn run_ps(call: &Call) -> Result<Value, ShellError> {
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,
});

View File

@ -112,13 +112,13 @@ pub fn disks(sys: &mut System, span: Span) -> Option<Value> {
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<Value> {
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<Value> {
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<Value> {
}
cols.push("uptime".into());
vals.push(Value::Duration {
val: 1000000000 * sys.uptime() as u64,
val: 1000000000 * sys.uptime() as i64,
span,
});

View File

@ -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<Operator, ShellError> {
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,
},
}
}

View File

@ -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 {

View File

@ -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<ParseError>) {
fn parse_decimal_str_to_number(decimal: &str) -> Option<i64> {
let string_to_parse = format!("0.{}", decimal);
if let Ok(x) = string_to_parse.parse::<f64>() {
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::<i64>().ok(), unit.0),
[number_str, decimal_part_str] => match unit.2 {
Some(unit_to_convert_to) => match (
number_str.parse::<i64>(),
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<ParseError>) {
fn parse_decimal_str_to_number(decimal: &str) -> Option<i64> {
let string_to_parse = format!("0.{}", decimal);
if let Ok(x) = string_to_parse.parse::<f64>() {
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::<i64>().ok(), unit.0),
[number_str, decimal_part_str] => match unit.2 {
Some(unit_to_convert_to) => match (
number_str.parse::<i64>(),
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),

View File

@ -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),
_ => {

View File

@ -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"

View File

@ -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<Expression>),
Table(Vec<Expression>, Vec<Vec<Expression>>),
Keyword(Vec<u8>, Span, Box<Expression>),
ValueWithUnit(Box<Expression>, Spanned<Unit>),
Filepath(String),
GlobPattern(String),
String(String), // FIXME: improve this in the future?

View File

@ -1,7 +1,11 @@
use miette::SourceSpan;
use serde::{Deserialize, Serialize};
pub struct Spanned<T> {
#[derive(Clone, Debug)]
pub struct Spanned<T>
where
T: Clone + std::fmt::Debug,
{
pub item: T,
pub span: Span,
}

View File

@ -12,6 +12,7 @@ pub enum Type {
Block,
CellPath,
Duration,
Date,
Filesize,
List(Box<Type>),
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"),

View File

@ -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<FixedOffset>,
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),
}
}

View File

@ -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,
}