mirror of
https://github.com/nushell/nushell.git
synced 2025-03-13 15:08:43 +01:00
Add Filesize
type (#14369)
# Description Adds a new `Filesize` type so that `FromValue` can be used to convert a `Value::Filesize` to a `Filesize`. Currently, to extract a filesize from a `Value` using `FromValue`, you have to extract an `i64` which coerces `Value::Int`, `Value::Duration`, and `Value::Filesize` to an `i64`. Having a separate type also allows us to enforce checked math to catch overflows. Similarly, it allows us to specify other trait implementations like `Display` in a common place. # User-Facing Changes Multiplication with filesizes now error on overflow. Should not be a breaking change for plugins (i.e., serialization) since `Filesize` is marked with `serde(transparent)`. # Tests + Formatting Updated some tests.
This commit is contained in:
parent
acca56f77c
commit
7f61cbbfd6
@ -203,7 +203,7 @@ pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
|||||||
Value::string(raw_string.trim(), span)
|
Value::string(raw_string.trim(), span)
|
||||||
}
|
}
|
||||||
Value::Int { val, .. } => convert_to_smallest_number_type(*val, span),
|
Value::Int { val, .. } => convert_to_smallest_number_type(*val, span),
|
||||||
Value::Filesize { val, .. } => convert_to_smallest_number_type(*val, span),
|
Value::Filesize { val, .. } => convert_to_smallest_number_type(val.get(), span),
|
||||||
Value::Duration { val, .. } => convert_to_smallest_number_type(*val, span),
|
Value::Duration { val, .. } => convert_to_smallest_number_type(*val, span),
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => {
|
||||||
let raw_bytes = val.as_bytes();
|
let raw_bytes = val.as_bytes();
|
||||||
|
@ -66,7 +66,7 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
|||||||
match input {
|
match input {
|
||||||
Value::Float { val, .. } => fmt_it_64(*val, span),
|
Value::Float { val, .. } => fmt_it_64(*val, span),
|
||||||
Value::Int { val, .. } => fmt_it(*val, span),
|
Value::Int { val, .. } => fmt_it(*val, span),
|
||||||
Value::Filesize { val, .. } => fmt_it(*val, span),
|
Value::Filesize { val, .. } => fmt_it(val.get(), span),
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
Value::Error { .. } => input.clone(),
|
Value::Error { .. } => input.clone(),
|
||||||
other => Value::error(
|
other => Value::error(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
use nu_protocol::{ShellError, Span, Value};
|
use nu_protocol::{Filesize, ShellError, Span, Value};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
/// A subset of [`Value`], which is hashable.
|
/// A subset of [`Value`], which is hashable.
|
||||||
@ -30,7 +30,7 @@ pub enum HashableValue {
|
|||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
Filesize {
|
Filesize {
|
||||||
val: i64,
|
val: Filesize,
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
Duration {
|
Duration {
|
||||||
@ -198,7 +198,10 @@ mod test {
|
|||||||
(Value::int(1, span), HashableValue::Int { val: 1, span }),
|
(Value::int(1, span), HashableValue::Int { val: 1, span }),
|
||||||
(
|
(
|
||||||
Value::filesize(1, span),
|
Value::filesize(1, span),
|
||||||
HashableValue::Filesize { val: 1, span },
|
HashableValue::Filesize {
|
||||||
|
val: 1.into(),
|
||||||
|
span,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Value::duration(1, span),
|
Value::duration(1, span),
|
||||||
|
@ -167,7 +167,7 @@ fn fill(
|
|||||||
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Int { val, .. } => fill_int(*val, args, span),
|
Value::Int { val, .. } => fill_int(*val, args, span),
|
||||||
Value::Filesize { val, .. } => fill_int(*val, args, span),
|
Value::Filesize { val, .. } => fill_int(val.get(), args, span),
|
||||||
Value::Float { val, .. } => fill_float(*val, args, span),
|
Value::Float { val, .. } => fill_float(*val, args, span),
|
||||||
Value::String { val, .. } => fill_string(val, args, span),
|
Value::String { val, .. } => fill_string(val, args, span),
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
@ -147,7 +147,7 @@ pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
|||||||
Value::Binary { .. } => input.clone(),
|
Value::Binary { .. } => input.clone(),
|
||||||
Value::Int { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
|
Value::Int { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
|
||||||
Value::Float { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
|
Value::Float { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
|
||||||
Value::Filesize { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
|
Value::Filesize { val, .. } => Value::binary(val.get().to_ne_bytes().to_vec(), span),
|
||||||
Value::String { val, .. } => Value::binary(val.as_bytes().to_vec(), span),
|
Value::String { val, .. } => Value::binary(val.as_bytes().to_vec(), span),
|
||||||
Value::Bool { val, .. } => Value::binary(i64::from(*val).to_ne_bytes().to_vec(), span),
|
Value::Bool { val, .. } => Value::binary(i64::from(*val).to_ne_bytes().to_vec(), span),
|
||||||
Value::Duration { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
|
Value::Duration { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
|
||||||
|
@ -253,7 +253,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
convert_int(input, span, radix)
|
convert_int(input, span, radix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Filesize { val, .. } => Value::int(*val, span),
|
Value::Filesize { val, .. } => Value::int(val.get(), span),
|
||||||
Value::Float { val, .. } => Value::int(
|
Value::Float { val, .. } => Value::int(
|
||||||
{
|
{
|
||||||
if radix == 10 {
|
if radix == 10 {
|
||||||
|
@ -421,7 +421,7 @@ pub fn value_to_sql(value: Value) -> Result<Box<dyn rusqlite::ToSql>, ShellError
|
|||||||
Value::Bool { val, .. } => Box::new(val),
|
Value::Bool { val, .. } => Box::new(val),
|
||||||
Value::Int { val, .. } => Box::new(val),
|
Value::Int { val, .. } => Box::new(val),
|
||||||
Value::Float { val, .. } => Box::new(val),
|
Value::Float { val, .. } => Box::new(val),
|
||||||
Value::Filesize { val, .. } => Box::new(val),
|
Value::Filesize { val, .. } => Box::new(val.get()),
|
||||||
Value::Duration { val, .. } => Box::new(val),
|
Value::Duration { val, .. } => Box::new(val),
|
||||||
Value::Date { val, .. } => Box::new(val),
|
Value::Date { val, .. } => Box::new(val),
|
||||||
Value::String { val, .. } => Box::new(val),
|
Value::String { val, .. } => Box::new(val),
|
||||||
|
@ -109,7 +109,7 @@ pub fn value_to_json_value(v: &Value) -> Result<nu_json::Value, ShellError> {
|
|||||||
let span = v.span();
|
let span = v.span();
|
||||||
Ok(match v {
|
Ok(match v {
|
||||||
Value::Bool { val, .. } => nu_json::Value::Bool(*val),
|
Value::Bool { val, .. } => nu_json::Value::Bool(*val),
|
||||||
Value::Filesize { val, .. } => nu_json::Value::I64(*val),
|
Value::Filesize { val, .. } => nu_json::Value::I64(val.get()),
|
||||||
Value::Duration { val, .. } => nu_json::Value::I64(*val),
|
Value::Duration { val, .. } => nu_json::Value::I64(*val),
|
||||||
Value::Date { val, .. } => nu_json::Value::String(val.to_string()),
|
Value::Date { val, .. } => nu_json::Value::String(val.to_string()),
|
||||||
Value::Float { val, .. } => nu_json::Value::F64(*val),
|
Value::Float { val, .. } => nu_json::Value::F64(*val),
|
||||||
|
@ -168,7 +168,7 @@ pub(crate) fn write_value(
|
|||||||
mp::write_f64(out, *val).err_span(span)?;
|
mp::write_f64(out, *val).err_span(span)?;
|
||||||
}
|
}
|
||||||
Value::Filesize { val, .. } => {
|
Value::Filesize { val, .. } => {
|
||||||
mp::write_sint(out, *val).err_span(span)?;
|
mp::write_sint(out, val.get()).err_span(span)?;
|
||||||
}
|
}
|
||||||
Value::Duration { val, .. } => {
|
Value::Duration { val, .. } => {
|
||||||
mp::write_sint(out, *val).err_span(span)?;
|
mp::write_sint(out, *val).err_span(span)?;
|
||||||
|
@ -47,7 +47,7 @@ fn helper(engine_state: &EngineState, v: &Value) -> Result<toml::Value, ShellErr
|
|||||||
Ok(match &v {
|
Ok(match &v {
|
||||||
Value::Bool { val, .. } => toml::Value::Boolean(*val),
|
Value::Bool { val, .. } => toml::Value::Boolean(*val),
|
||||||
Value::Int { val, .. } => toml::Value::Integer(*val),
|
Value::Int { val, .. } => toml::Value::Integer(*val),
|
||||||
Value::Filesize { val, .. } => toml::Value::Integer(*val),
|
Value::Filesize { val, .. } => toml::Value::Integer(val.get()),
|
||||||
Value::Duration { val, .. } => toml::Value::String(val.to_string()),
|
Value::Duration { val, .. } => toml::Value::String(val.to_string()),
|
||||||
Value::Date { val, .. } => toml::Value::Datetime(to_toml_datetime(val)),
|
Value::Date { val, .. } => toml::Value::Datetime(to_toml_datetime(val)),
|
||||||
Value::Range { .. } => toml::Value::String("<Range>".to_string()),
|
Value::Range { .. } => toml::Value::String("<Range>".to_string()),
|
||||||
|
@ -44,7 +44,9 @@ pub fn value_to_yaml_value(v: &Value) -> Result<serde_yaml::Value, ShellError> {
|
|||||||
Ok(match &v {
|
Ok(match &v {
|
||||||
Value::Bool { val, .. } => serde_yaml::Value::Bool(*val),
|
Value::Bool { val, .. } => serde_yaml::Value::Bool(*val),
|
||||||
Value::Int { val, .. } => serde_yaml::Value::Number(serde_yaml::Number::from(*val)),
|
Value::Int { val, .. } => serde_yaml::Value::Number(serde_yaml::Number::from(*val)),
|
||||||
Value::Filesize { val, .. } => serde_yaml::Value::Number(serde_yaml::Number::from(*val)),
|
Value::Filesize { val, .. } => {
|
||||||
|
serde_yaml::Value::Number(serde_yaml::Number::from(val.get()))
|
||||||
|
}
|
||||||
Value::Duration { val, .. } => serde_yaml::Value::String(val.to_string()),
|
Value::Duration { val, .. } => serde_yaml::Value::String(val.to_string()),
|
||||||
Value::Date { val, .. } => serde_yaml::Value::String(val.to_string()),
|
Value::Date { val, .. } => serde_yaml::Value::String(val.to_string()),
|
||||||
Value::Range { .. } => serde_yaml::Value::Null,
|
Value::Range { .. } => serde_yaml::Value::Null,
|
||||||
|
@ -90,7 +90,7 @@ pub fn average(values: &[Value], span: Span, head: Span) -> Result<Value, ShellE
|
|||||||
let total = &sum(Value::int(0, head), values.to_vec(), span, head)?;
|
let total = &sum(Value::int(0, head), values.to_vec(), span, head)?;
|
||||||
let span = total.span();
|
let span = total.span();
|
||||||
match total {
|
match total {
|
||||||
Value::Filesize { val, .. } => Ok(Value::filesize(val / values.len() as i64, span)),
|
Value::Filesize { val, .. } => Ok(Value::filesize(val.get() / values.len() as i64, span)),
|
||||||
Value::Duration { val, .. } => Ok(Value::duration(val / values.len() as i64, span)),
|
Value::Duration { val, .. } => Ok(Value::duration(val / values.len() as i64, span)),
|
||||||
_ => total.div(head, &Value::int(values.len() as i64, head), head),
|
_ => total.div(head, &Value::int(values.len() as i64, head), head),
|
||||||
}
|
}
|
||||||
|
@ -142,9 +142,10 @@ pub fn mode(values: &[Value], _span: Span, head: Span) -> Result<Value, ShellErr
|
|||||||
Value::Float { val, .. } => {
|
Value::Float { val, .. } => {
|
||||||
Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Float))
|
Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Float))
|
||||||
}
|
}
|
||||||
Value::Filesize { val, .. } => {
|
Value::Filesize { val, .. } => Ok(HashableType::new(
|
||||||
Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Filesize))
|
val.get().to_ne_bytes(),
|
||||||
}
|
NumberTypes::Filesize,
|
||||||
|
)),
|
||||||
Value::Error { error, .. } => Err(*error.clone()),
|
Value::Error { error, .. } => Err(*error.clone()),
|
||||||
other => Err(ShellError::UnsupportedInput {
|
other => Err(ShellError::UnsupportedInput {
|
||||||
msg: "Unable to give a result with this input".to_string(),
|
msg: "Unable to give a result with this input".to_string(),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::format_filesize_from_conf;
|
||||||
use rand::{thread_rng, RngCore};
|
use rand::{thread_rng, RngCore};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -37,7 +37,27 @@ impl Command for SubCommand {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let length = call.req(engine_state, stack, 0)?;
|
let length_val = call.req(engine_state, stack, 0)?;
|
||||||
|
let length = match length_val {
|
||||||
|
Value::Int { val, .. } => usize::try_from(val).map_err(|_| ShellError::InvalidValue {
|
||||||
|
valid: "a non-negative int or filesize".into(),
|
||||||
|
actual: val.to_string(),
|
||||||
|
span: length_val.span(),
|
||||||
|
}),
|
||||||
|
Value::Filesize { val, .. } => {
|
||||||
|
usize::try_from(val).map_err(|_| ShellError::InvalidValue {
|
||||||
|
valid: "a non-negative int or filesize".into(),
|
||||||
|
actual: format_filesize_from_conf(val, engine_state.get_config()),
|
||||||
|
span: length_val.span(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
val => Err(ShellError::RuntimeTypeMismatch {
|
||||||
|
expected: Type::custom("int or filesize"),
|
||||||
|
actual: val.get_type(),
|
||||||
|
span: val.span(),
|
||||||
|
}),
|
||||||
|
}?;
|
||||||
|
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
let mut out = vec![0u8; length];
|
let mut out = vec![0u8; length];
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::format_filesize_from_conf;
|
||||||
use rand::{
|
use rand::{
|
||||||
distributions::{Alphanumeric, Distribution},
|
distributions::{Alphanumeric, Distribution},
|
||||||
thread_rng,
|
thread_rng,
|
||||||
@ -73,14 +73,36 @@ fn chars(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let span = call.head;
|
let span = call.head;
|
||||||
let length: Option<usize> = call.get_flag(engine_state, stack, "length")?;
|
let length: Option<Value> = call.get_flag(engine_state, stack, "length")?;
|
||||||
|
let length = if let Some(length_val) = length {
|
||||||
|
match length_val {
|
||||||
|
Value::Int { val, .. } => usize::try_from(val).map_err(|_| ShellError::InvalidValue {
|
||||||
|
valid: "a non-negative int or filesize".into(),
|
||||||
|
actual: val.to_string(),
|
||||||
|
span: length_val.span(),
|
||||||
|
}),
|
||||||
|
Value::Filesize { val, .. } => {
|
||||||
|
usize::try_from(val).map_err(|_| ShellError::InvalidValue {
|
||||||
|
valid: "a non-negative int or filesize".into(),
|
||||||
|
actual: format_filesize_from_conf(val, engine_state.get_config()),
|
||||||
|
span: length_val.span(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
val => Err(ShellError::RuntimeTypeMismatch {
|
||||||
|
expected: Type::custom("int or filesize"),
|
||||||
|
actual: val.get_type(),
|
||||||
|
span: val.span(),
|
||||||
|
}),
|
||||||
|
}?
|
||||||
|
} else {
|
||||||
|
DEFAULT_CHARS_LENGTH
|
||||||
|
};
|
||||||
|
|
||||||
let chars_length = length.unwrap_or(DEFAULT_CHARS_LENGTH);
|
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
let random_string = Alphanumeric
|
let random_string = Alphanumeric
|
||||||
.sample_iter(&mut rng)
|
.sample_iter(&mut rng)
|
||||||
.take(chars_length)
|
.take(length)
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@ use itertools::Itertools;
|
|||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_engine::DIR_VAR_PARSER_INFO;
|
use nu_engine::DIR_VAR_PARSER_INFO;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::*, engine::StateWorkingSet, eval_const::eval_constant, BlockId, DeclId, DidYouMean, Flag,
|
ast::*, engine::StateWorkingSet, eval_const::eval_constant, BlockId, DeclId, DidYouMean,
|
||||||
ParseError, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, VarId, ENV_VARIABLE_ID,
|
FilesizeUnit, Flag, ParseError, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type,
|
||||||
IN_VARIABLE_ID,
|
VarId, ENV_VARIABLE_ID, IN_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
@ -2575,19 +2575,67 @@ pub fn parse_unit_value<'res>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const FILESIZE_UNIT_GROUPS: &[UnitGroup] = &[
|
pub const FILESIZE_UNIT_GROUPS: &[UnitGroup] = &[
|
||||||
(Unit::Kilobyte, "KB", Some((Unit::Byte, 1000))),
|
(
|
||||||
(Unit::Megabyte, "MB", Some((Unit::Kilobyte, 1000))),
|
Unit::Filesize(FilesizeUnit::KB),
|
||||||
(Unit::Gigabyte, "GB", Some((Unit::Megabyte, 1000))),
|
"KB",
|
||||||
(Unit::Terabyte, "TB", Some((Unit::Gigabyte, 1000))),
|
Some((Unit::Filesize(FilesizeUnit::B), 1000)),
|
||||||
(Unit::Petabyte, "PB", Some((Unit::Terabyte, 1000))),
|
),
|
||||||
(Unit::Exabyte, "EB", Some((Unit::Petabyte, 1000))),
|
(
|
||||||
(Unit::Kibibyte, "KIB", Some((Unit::Byte, 1024))),
|
Unit::Filesize(FilesizeUnit::MB),
|
||||||
(Unit::Mebibyte, "MIB", Some((Unit::Kibibyte, 1024))),
|
"MB",
|
||||||
(Unit::Gibibyte, "GIB", Some((Unit::Mebibyte, 1024))),
|
Some((Unit::Filesize(FilesizeUnit::KB), 1000)),
|
||||||
(Unit::Tebibyte, "TIB", Some((Unit::Gibibyte, 1024))),
|
),
|
||||||
(Unit::Pebibyte, "PIB", Some((Unit::Tebibyte, 1024))),
|
(
|
||||||
(Unit::Exbibyte, "EIB", Some((Unit::Pebibyte, 1024))),
|
Unit::Filesize(FilesizeUnit::GB),
|
||||||
(Unit::Byte, "B", None),
|
"GB",
|
||||||
|
Some((Unit::Filesize(FilesizeUnit::MB), 1000)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Unit::Filesize(FilesizeUnit::TB),
|
||||||
|
"TB",
|
||||||
|
Some((Unit::Filesize(FilesizeUnit::GB), 1000)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Unit::Filesize(FilesizeUnit::PB),
|
||||||
|
"PB",
|
||||||
|
Some((Unit::Filesize(FilesizeUnit::TB), 1000)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Unit::Filesize(FilesizeUnit::EB),
|
||||||
|
"EB",
|
||||||
|
Some((Unit::Filesize(FilesizeUnit::PB), 1000)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Unit::Filesize(FilesizeUnit::KiB),
|
||||||
|
"KIB",
|
||||||
|
Some((Unit::Filesize(FilesizeUnit::B), 1024)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Unit::Filesize(FilesizeUnit::MiB),
|
||||||
|
"MIB",
|
||||||
|
Some((Unit::Filesize(FilesizeUnit::KiB), 1024)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Unit::Filesize(FilesizeUnit::GiB),
|
||||||
|
"GIB",
|
||||||
|
Some((Unit::Filesize(FilesizeUnit::MiB), 1024)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Unit::Filesize(FilesizeUnit::TiB),
|
||||||
|
"TIB",
|
||||||
|
Some((Unit::Filesize(FilesizeUnit::GiB), 1024)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Unit::Filesize(FilesizeUnit::PiB),
|
||||||
|
"PIB",
|
||||||
|
Some((Unit::Filesize(FilesizeUnit::TiB), 1024)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Unit::Filesize(FilesizeUnit::EiB),
|
||||||
|
"EIB",
|
||||||
|
Some((Unit::Filesize(FilesizeUnit::EiB), 1024)),
|
||||||
|
),
|
||||||
|
(Unit::Filesize(FilesizeUnit::B), "B", None),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const DURATION_UNIT_GROUPS: &[UnitGroup] = &[
|
pub const DURATION_UNIT_GROUPS: &[UnitGroup] = &[
|
||||||
|
@ -1,24 +1,9 @@
|
|||||||
use crate::{ShellError, Span, Value};
|
use crate::{Filesize, FilesizeUnit, IntoValue, ShellError, Span, Value};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum Unit {
|
pub enum Unit {
|
||||||
// Filesize units: metric
|
Filesize(FilesizeUnit),
|
||||||
Byte,
|
|
||||||
Kilobyte,
|
|
||||||
Megabyte,
|
|
||||||
Gigabyte,
|
|
||||||
Terabyte,
|
|
||||||
Petabyte,
|
|
||||||
Exabyte,
|
|
||||||
|
|
||||||
// Filesize units: ISO/IEC 80000
|
|
||||||
Kibibyte,
|
|
||||||
Mebibyte,
|
|
||||||
Gibibyte,
|
|
||||||
Tebibyte,
|
|
||||||
Pebibyte,
|
|
||||||
Exbibyte,
|
|
||||||
|
|
||||||
// Duration units
|
// Duration units
|
||||||
Nanosecond,
|
Nanosecond,
|
||||||
@ -34,33 +19,19 @@ pub enum Unit {
|
|||||||
impl Unit {
|
impl Unit {
|
||||||
pub fn build_value(self, size: i64, span: Span) -> Result<Value, ShellError> {
|
pub fn build_value(self, size: i64, span: Span) -> Result<Value, ShellError> {
|
||||||
match self {
|
match self {
|
||||||
Unit::Byte => Ok(Value::filesize(size, span)),
|
Unit::Filesize(unit) => {
|
||||||
Unit::Kilobyte => Ok(Value::filesize(size * 1000, span)),
|
if let Some(filesize) = Filesize::from_unit(size, unit) {
|
||||||
Unit::Megabyte => Ok(Value::filesize(size * 1000 * 1000, span)),
|
Ok(filesize.into_value(span))
|
||||||
Unit::Gigabyte => Ok(Value::filesize(size * 1000 * 1000 * 1000, span)),
|
} else {
|
||||||
Unit::Terabyte => Ok(Value::filesize(size * 1000 * 1000 * 1000 * 1000, span)),
|
Err(ShellError::GenericError {
|
||||||
Unit::Petabyte => Ok(Value::filesize(
|
error: "filesize too large".into(),
|
||||||
size * 1000 * 1000 * 1000 * 1000 * 1000,
|
msg: "filesize too large".into(),
|
||||||
span,
|
span: Some(span),
|
||||||
)),
|
help: None,
|
||||||
Unit::Exabyte => Ok(Value::filesize(
|
inner: vec![],
|
||||||
size * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
|
})
|
||||||
span,
|
}
|
||||||
)),
|
}
|
||||||
|
|
||||||
Unit::Kibibyte => Ok(Value::filesize(size * 1024, span)),
|
|
||||||
Unit::Mebibyte => Ok(Value::filesize(size * 1024 * 1024, span)),
|
|
||||||
Unit::Gibibyte => Ok(Value::filesize(size * 1024 * 1024 * 1024, span)),
|
|
||||||
Unit::Tebibyte => Ok(Value::filesize(size * 1024 * 1024 * 1024 * 1024, span)),
|
|
||||||
Unit::Pebibyte => Ok(Value::filesize(
|
|
||||||
size * 1024 * 1024 * 1024 * 1024 * 1024,
|
|
||||||
span,
|
|
||||||
)),
|
|
||||||
Unit::Exbibyte => Ok(Value::filesize(
|
|
||||||
size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
|
|
||||||
span,
|
|
||||||
)),
|
|
||||||
|
|
||||||
Unit::Nanosecond => Ok(Value::duration(size, span)),
|
Unit::Nanosecond => Ok(Value::duration(size, span)),
|
||||||
Unit::Microsecond => Ok(Value::duration(size * 1000, span)),
|
Unit::Microsecond => Ok(Value::duration(size * 1000, span)),
|
||||||
Unit::Millisecond => Ok(Value::duration(size * 1000 * 1000, span)),
|
Unit::Millisecond => Ok(Value::duration(size * 1000 * 1000, span)),
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
use std::{fmt, sync::Arc};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{CellPath, Expression, Operator, Pattern, RangeInclusion},
|
ast::{CellPath, Expression, Operator, Pattern, RangeInclusion},
|
||||||
engine::EngineState,
|
engine::EngineState,
|
||||||
BlockId, DeclId, RegId, Span, Value, VarId,
|
BlockId, DeclId, Filesize, RegId, Span, Value, VarId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{fmt, sync::Arc};
|
||||||
|
|
||||||
mod call;
|
mod call;
|
||||||
mod display;
|
mod display;
|
||||||
@ -397,7 +395,7 @@ pub enum Literal {
|
|||||||
Bool(bool),
|
Bool(bool),
|
||||||
Int(i64),
|
Int(i64),
|
||||||
Float(f64),
|
Float(f64),
|
||||||
Filesize(i64),
|
Filesize(Filesize),
|
||||||
Duration(i64),
|
Duration(i64),
|
||||||
Binary(DataSlice),
|
Binary(DataSlice),
|
||||||
Block(BlockId),
|
Block(BlockId),
|
||||||
|
@ -1,13 +1,442 @@
|
|||||||
use crate::Config;
|
use crate::{Config, FromValue, IntoValue, ShellError, Span, Type, Value};
|
||||||
use byte_unit::UnitType;
|
use byte_unit::UnitType;
|
||||||
use nu_utils::get_system_locale;
|
use nu_utils::get_system_locale;
|
||||||
use num_format::ToFormattedString;
|
use num_format::ToFormattedString;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
iter::Sum,
|
||||||
|
ops::{Add, Mul, Neg, Sub},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
pub fn format_filesize_from_conf(num_bytes: i64, config: &Config) -> String {
|
/// A signed number of bytes.
|
||||||
|
///
|
||||||
|
/// [`Filesize`] is a wrapper around [`i64`]. Whereas [`i64`] is a dimensionless value, [`Filesize`] represents a
|
||||||
|
/// numerical value with a dimensional unit (byte).
|
||||||
|
///
|
||||||
|
/// A [`Filesize`] can be created from an [`i64`] using [`Filesize::new`] or the `From` or `Into` trait implementations.
|
||||||
|
/// To get the underlying [`i64`] value, use [`Filesize::get`] or the `From` or `Into` trait implementations.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct Filesize(i64);
|
||||||
|
|
||||||
|
impl Filesize {
|
||||||
|
/// A [`Filesize`] of 0 bytes.
|
||||||
|
pub const ZERO: Self = Self(0);
|
||||||
|
|
||||||
|
/// The smallest possible [`Filesize`] value.
|
||||||
|
pub const MIN: Self = Self(i64::MIN);
|
||||||
|
|
||||||
|
/// The largest possible [`Filesize`] value.
|
||||||
|
pub const MAX: Self = Self(i64::MAX);
|
||||||
|
|
||||||
|
/// Create a new [`Filesize`] from a [`i64`] number of bytes.
|
||||||
|
pub const fn new(bytes: i64) -> Self {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`Filesize`] from a signed multiple of a [`FilesizeUnit`].
|
||||||
|
///
|
||||||
|
/// If the resulting number of bytes calculated by `value * unit.as_bytes()` overflows an
|
||||||
|
/// [`i64`], then `None` is returned.
|
||||||
|
pub const fn from_unit(value: i64, unit: FilesizeUnit) -> Option<Self> {
|
||||||
|
if let Some(bytes) = value.checked_mul(unit.as_bytes() as i64) {
|
||||||
|
Some(Self(bytes))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the underlying [`i64`] number of bytes in a [`Filesize`].
|
||||||
|
pub const fn get(&self) -> i64 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if a [`Filesize`] is positive and false if it is zero or negative.
|
||||||
|
pub const fn is_positive(self) -> bool {
|
||||||
|
self.0.is_positive()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if a [`Filesize`] is negative and false if it is zero or positive.
|
||||||
|
pub const fn is_negative(self) -> bool {
|
||||||
|
self.0.is_negative()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a [`Filesize`] representing the sign of `self`.
|
||||||
|
/// - 0 if the filesize is zero
|
||||||
|
/// - 1 if the filesize is positive
|
||||||
|
/// - -1 if the filesize is negative
|
||||||
|
pub const fn signum(self) -> Self {
|
||||||
|
Self(self.0.signum())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i64> for Filesize {
|
||||||
|
fn from(value: i64) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Filesize> for i64 {
|
||||||
|
fn from(filesize: Filesize) -> Self {
|
||||||
|
filesize.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_from {
|
||||||
|
($($ty:ty),* $(,)?) => {
|
||||||
|
$(
|
||||||
|
impl From<$ty> for Filesize {
|
||||||
|
#[inline]
|
||||||
|
fn from(value: $ty) -> Self {
|
||||||
|
Self(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Filesize> for $ty {
|
||||||
|
type Error = <i64 as TryInto<$ty>>::Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(filesize: Filesize) -> Result<Self, Self::Error> {
|
||||||
|
filesize.0.try_into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_from!(u8, i8, u16, i16, u32, i32);
|
||||||
|
|
||||||
|
macro_rules! impl_try_from {
|
||||||
|
($($ty:ty),* $(,)?) => {
|
||||||
|
$(
|
||||||
|
impl TryFrom<$ty> for Filesize {
|
||||||
|
type Error = <$ty as TryInto<i64>>::Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(value: $ty) -> Result<Self, Self::Error> {
|
||||||
|
value.try_into().map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Filesize> for $ty {
|
||||||
|
type Error = <i64 as TryInto<$ty>>::Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(filesize: Filesize) -> Result<Self, Self::Error> {
|
||||||
|
filesize.0.try_into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_try_from!(u64, usize, isize);
|
||||||
|
|
||||||
|
/// The error type returned when a checked conversion from a floating point type fails.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)]
|
||||||
|
pub struct TryFromFloatError(());
|
||||||
|
|
||||||
|
impl fmt::Display for TryFromFloatError {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(fmt, "out of range float type conversion attempted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<f64> for Filesize {
|
||||||
|
type Error = TryFromFloatError;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(value: f64) -> Result<Self, Self::Error> {
|
||||||
|
if i64::MIN as f64 <= value && value <= i64::MAX as f64 {
|
||||||
|
Ok(Self(value as i64))
|
||||||
|
} else {
|
||||||
|
Err(TryFromFloatError(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<f32> for Filesize {
|
||||||
|
type Error = TryFromFloatError;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(value: f32) -> Result<Self, Self::Error> {
|
||||||
|
if i64::MIN as f32 <= value && value <= i64::MAX as f32 {
|
||||||
|
Ok(Self(value as i64))
|
||||||
|
} else {
|
||||||
|
Err(TryFromFloatError(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromValue for Filesize {
|
||||||
|
fn from_value(value: Value) -> Result<Self, ShellError> {
|
||||||
|
value.as_filesize()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type() -> Type {
|
||||||
|
Type::Filesize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoValue for Filesize {
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
Value::filesize(self.0, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for Filesize {
|
||||||
|
type Output = Option<Self>;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
self.0.checked_add(rhs.0).map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for Filesize {
|
||||||
|
type Output = Option<Self>;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
self.0.checked_sub(rhs.0).map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<i64> for Filesize {
|
||||||
|
type Output = Option<Self>;
|
||||||
|
|
||||||
|
fn mul(self, rhs: i64) -> Self::Output {
|
||||||
|
self.0.checked_mul(rhs).map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<Filesize> for i64 {
|
||||||
|
type Output = Option<Filesize>;
|
||||||
|
|
||||||
|
fn mul(self, rhs: Filesize) -> Self::Output {
|
||||||
|
self.checked_mul(rhs.0).map(Filesize::new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<f64> for Filesize {
|
||||||
|
type Output = Option<Self>;
|
||||||
|
|
||||||
|
fn mul(self, rhs: f64) -> Self::Output {
|
||||||
|
let bytes = ((self.0 as f64) * rhs).round();
|
||||||
|
if i64::MIN as f64 <= bytes && bytes <= i64::MAX as f64 {
|
||||||
|
Some(Self(bytes as i64))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<Filesize> for f64 {
|
||||||
|
type Output = Option<Filesize>;
|
||||||
|
|
||||||
|
fn mul(self, rhs: Filesize) -> Self::Output {
|
||||||
|
let bytes = (self * (rhs.0 as f64)).round();
|
||||||
|
if i64::MIN as f64 <= bytes && bytes <= i64::MAX as f64 {
|
||||||
|
Some(Filesize(bytes as i64))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Neg for Filesize {
|
||||||
|
type Output = Option<Self>;
|
||||||
|
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
self.0.checked_neg().map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sum<Filesize> for Option<Filesize> {
|
||||||
|
fn sum<I: Iterator<Item = Filesize>>(iter: I) -> Self {
|
||||||
|
let mut sum = Filesize::ZERO;
|
||||||
|
for filesize in iter {
|
||||||
|
sum = (sum + filesize)?;
|
||||||
|
}
|
||||||
|
Some(sum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Filesize {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
format_filesize(*self, "auto", Some(false)).fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All the possible filesize units for a [`Filesize`].
|
||||||
|
///
|
||||||
|
/// This type contains both units with metric (SI) decimal prefixes which are powers of 10 (e.g., kB = 1000 bytes)
|
||||||
|
/// and units with binary prefixes which are powers of 2 (e.g., KiB = 1024 bytes).
|
||||||
|
///
|
||||||
|
/// The number of bytes in a [`FilesizeUnit`] can be obtained using
|
||||||
|
/// [`as_bytes`](FilesizeUnit::as_bytes).
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum FilesizeUnit {
|
||||||
|
/// One byte
|
||||||
|
B,
|
||||||
|
/// Kilobyte = 1000 bytes
|
||||||
|
KB,
|
||||||
|
/// Megabyte = 10<sup>6</sup> bytes
|
||||||
|
MB,
|
||||||
|
/// Gigabyte = 10<sup>9</sup> bytes
|
||||||
|
GB,
|
||||||
|
/// Terabyte = 10<sup>12</sup> bytes
|
||||||
|
TB,
|
||||||
|
/// Petabyte = 10<sup>15</sup> bytes
|
||||||
|
PB,
|
||||||
|
/// Exabyte = 10<sup>18</sup> bytes
|
||||||
|
EB,
|
||||||
|
/// Kibibyte = 1024 bytes
|
||||||
|
KiB,
|
||||||
|
/// Mebibyte = 2<sup>20</sup> bytes
|
||||||
|
MiB,
|
||||||
|
/// Gibibyte = 2<sup>30</sup> bytes
|
||||||
|
GiB,
|
||||||
|
/// Tebibyte = 2<sup>40</sup> bytes
|
||||||
|
TiB,
|
||||||
|
/// Pebibyte = 2<sup>50</sup> bytes
|
||||||
|
PiB,
|
||||||
|
/// Exbibyte = 2<sup>60</sup> bytes
|
||||||
|
EiB,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilesizeUnit {
|
||||||
|
/// Returns the number of bytes in a [`FilesizeUnit`].
|
||||||
|
pub const fn as_bytes(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
Self::B => 1,
|
||||||
|
Self::KB => 10_u64.pow(3),
|
||||||
|
Self::MB => 10_u64.pow(6),
|
||||||
|
Self::GB => 10_u64.pow(9),
|
||||||
|
Self::TB => 10_u64.pow(12),
|
||||||
|
Self::PB => 10_u64.pow(15),
|
||||||
|
Self::EB => 10_u64.pow(18),
|
||||||
|
Self::KiB => 2_u64.pow(10),
|
||||||
|
Self::MiB => 2_u64.pow(20),
|
||||||
|
Self::GiB => 2_u64.pow(30),
|
||||||
|
Self::TiB => 2_u64.pow(40),
|
||||||
|
Self::PiB => 2_u64.pow(50),
|
||||||
|
Self::EiB => 2_u64.pow(60),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a [`FilesizeUnit`] to a [`Filesize`].
|
||||||
|
///
|
||||||
|
/// To create a [`Filesize`] from a multiple of a [`FilesizeUnit`] use [`Filesize::from_unit`].
|
||||||
|
pub const fn as_filesize(&self) -> Filesize {
|
||||||
|
Filesize::new(self.as_bytes() as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the abbreviated unit for a [`FilesizeUnit`] as a [`str`].
|
||||||
|
///
|
||||||
|
/// The abbreviated unit is exactly the same as the enum case name in Rust code.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use nu_protocol::FilesizeUnit;
|
||||||
|
/// assert_eq!(FilesizeUnit::B.as_str(), "B");
|
||||||
|
/// assert_eq!(FilesizeUnit::KB.as_str(), "kB");
|
||||||
|
/// assert_eq!(FilesizeUnit::KiB.as_str(), "KiB");
|
||||||
|
/// ```
|
||||||
|
pub const fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::B => "B",
|
||||||
|
Self::KB => "kB",
|
||||||
|
Self::MB => "MB",
|
||||||
|
Self::GB => "GB",
|
||||||
|
Self::TB => "TB",
|
||||||
|
Self::PB => "PB",
|
||||||
|
Self::EB => "EB",
|
||||||
|
Self::KiB => "KiB",
|
||||||
|
Self::MiB => "MiB",
|
||||||
|
Self::GiB => "GiB",
|
||||||
|
Self::TiB => "TiB",
|
||||||
|
Self::PiB => "PiB",
|
||||||
|
Self::EiB => "EiB",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if a [`FilesizeUnit`] has a metric (SI) decimal prefix (a power of 10).
|
||||||
|
///
|
||||||
|
/// Note that this returns `true` for [`FilesizeUnit::B`] as well.
|
||||||
|
pub const fn is_decimal(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::B | Self::KB | Self::MB | Self::GB | Self::TB | Self::PB | Self::EB => true,
|
||||||
|
Self::KiB | Self::MiB | Self::GiB | Self::TiB | Self::PiB | Self::EiB => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if a [`FilesizeUnit`] has a binary prefix (a power of 2).
|
||||||
|
///
|
||||||
|
/// Note that this returns `true` for [`FilesizeUnit::B`] as well.
|
||||||
|
pub const fn is_binary(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::KB | Self::MB | Self::GB | Self::TB | Self::PB | Self::EB => false,
|
||||||
|
Self::B | Self::KiB | Self::MiB | Self::GiB | Self::TiB | Self::PiB | Self::EiB => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FilesizeUnit> for Filesize {
|
||||||
|
fn from(unit: FilesizeUnit) -> Self {
|
||||||
|
unit.as_filesize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error returned when failing to parse a [`FilesizeUnit`].
|
||||||
|
///
|
||||||
|
/// This occurs when the string being parsed does not exactly match the name of one of the
|
||||||
|
/// enum cases in [`FilesizeUnit`].
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)]
|
||||||
|
pub struct ParseFilesizeUnitError(());
|
||||||
|
|
||||||
|
impl fmt::Display for ParseFilesizeUnitError {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(fmt, "invalid filesize unit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FilesizeUnit {
|
||||||
|
type Err = ParseFilesizeUnitError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"B" => Self::B,
|
||||||
|
"kB" => Self::KB,
|
||||||
|
"MB" => Self::MB,
|
||||||
|
"GB" => Self::GB,
|
||||||
|
"TB" => Self::TB,
|
||||||
|
"PB" => Self::PB,
|
||||||
|
"EB" => Self::EB,
|
||||||
|
"KiB" => Self::KiB,
|
||||||
|
"MiB" => Self::MiB,
|
||||||
|
"GiB" => Self::GiB,
|
||||||
|
"TiB" => Self::TiB,
|
||||||
|
"PiB" => Self::PiB,
|
||||||
|
"EiB" => Self::EiB,
|
||||||
|
_ => return Err(ParseFilesizeUnitError(())),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FilesizeUnit {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.as_str().fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_filesize_from_conf(filesize: Filesize, config: &Config) -> String {
|
||||||
// We need to take into account config.filesize_metric so, if someone asks for KB
|
// We need to take into account config.filesize_metric so, if someone asks for KB
|
||||||
// and filesize_metric is false, return KiB
|
// and filesize_metric is false, return KiB
|
||||||
format_filesize(
|
format_filesize(
|
||||||
num_bytes,
|
filesize,
|
||||||
&config.filesize.format,
|
&config.filesize.format,
|
||||||
Some(config.filesize.metric),
|
Some(config.filesize.metric),
|
||||||
)
|
)
|
||||||
@ -16,7 +445,7 @@ pub fn format_filesize_from_conf(num_bytes: i64, config: &Config) -> String {
|
|||||||
// filesize_metric is explicit when printed a value according to user config;
|
// filesize_metric is explicit when printed a value according to user config;
|
||||||
// other places (such as `format filesize`) don't.
|
// other places (such as `format filesize`) don't.
|
||||||
pub fn format_filesize(
|
pub fn format_filesize(
|
||||||
num_bytes: i64,
|
filesize: Filesize,
|
||||||
format_value: &str,
|
format_value: &str,
|
||||||
filesize_metric: Option<bool>,
|
filesize_metric: Option<bool>,
|
||||||
) -> String {
|
) -> String {
|
||||||
@ -25,7 +454,7 @@ pub fn format_filesize(
|
|||||||
// When format_value is "auto" or an invalid value, the returned ByteUnit doesn't matter
|
// When format_value is "auto" or an invalid value, the returned ByteUnit doesn't matter
|
||||||
// and is always B.
|
// and is always B.
|
||||||
let filesize_unit = get_filesize_format(format_value, filesize_metric);
|
let filesize_unit = get_filesize_format(format_value, filesize_metric);
|
||||||
let byte = byte_unit::Byte::from_u64(num_bytes.unsigned_abs());
|
let byte = byte_unit::Byte::from_u64(filesize.0.unsigned_abs());
|
||||||
let adj_byte = if let Some(unit) = filesize_unit {
|
let adj_byte = if let Some(unit) = filesize_unit {
|
||||||
byte.get_adjusted_unit(unit)
|
byte.get_adjusted_unit(unit)
|
||||||
} else {
|
} else {
|
||||||
@ -43,7 +472,7 @@ pub fn format_filesize(
|
|||||||
let locale = get_system_locale();
|
let locale = get_system_locale();
|
||||||
let locale_byte = adj_byte.get_value() as u64;
|
let locale_byte = adj_byte.get_value() as u64;
|
||||||
let locale_byte_string = locale_byte.to_formatted_string(&locale);
|
let locale_byte_string = locale_byte.to_formatted_string(&locale);
|
||||||
let locale_signed_byte_string = if num_bytes.is_negative() {
|
let locale_signed_byte_string = if filesize.is_negative() {
|
||||||
format!("-{locale_byte_string}")
|
format!("-{locale_byte_string}")
|
||||||
} else {
|
} else {
|
||||||
locale_byte_string
|
locale_byte_string
|
||||||
@ -56,7 +485,7 @@ pub fn format_filesize(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if num_bytes.is_negative() {
|
if filesize.is_negative() {
|
||||||
format!("-{:.1}", adj_byte)
|
format!("-{:.1}", adj_byte)
|
||||||
} else {
|
} else {
|
||||||
format!("{:.1}", adj_byte)
|
format!("{:.1}", adj_byte)
|
||||||
@ -116,6 +545,9 @@ mod tests {
|
|||||||
#[case] filesize_format: String,
|
#[case] filesize_format: String,
|
||||||
#[case] exp: &str,
|
#[case] exp: &str,
|
||||||
) {
|
) {
|
||||||
assert_eq!(exp, format_filesize(val, &filesize_format, filesize_metric));
|
assert_eq!(
|
||||||
|
exp,
|
||||||
|
format_filesize(Filesize::new(val), &filesize_format, filesize_metric)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,9 +252,7 @@ impl FromValue for i64 {
|
|||||||
fn from_value(v: Value) -> Result<Self, ShellError> {
|
fn from_value(v: Value) -> Result<Self, ShellError> {
|
||||||
match v {
|
match v {
|
||||||
Value::Int { val, .. } => Ok(val),
|
Value::Int { val, .. } => Ok(val),
|
||||||
Value::Filesize { val, .. } => Ok(val),
|
|
||||||
Value::Duration { val, .. } => Ok(val),
|
Value::Duration { val, .. } => Ok(val),
|
||||||
|
|
||||||
v => Err(ShellError::CantConvert {
|
v => Err(ShellError::CantConvert {
|
||||||
to_type: Self::expected_type().to_string(),
|
to_type: Self::expected_type().to_string(),
|
||||||
from_type: v.get_type().to_string(),
|
from_type: v.get_type().to_string(),
|
||||||
@ -308,9 +306,7 @@ macro_rules! impl_from_value_for_uint {
|
|||||||
let span = v.span();
|
let span = v.span();
|
||||||
const MAX: i64 = $max;
|
const MAX: i64 = $max;
|
||||||
match v {
|
match v {
|
||||||
Value::Int { val, .. }
|
Value::Int { val, .. } | Value::Duration { val, .. } => {
|
||||||
| Value::Filesize { val, .. }
|
|
||||||
| Value::Duration { val, .. } => {
|
|
||||||
match val {
|
match val {
|
||||||
i64::MIN..=-1 => Err(ShellError::NeedsPositiveValue { span }),
|
i64::MIN..=-1 => Err(ShellError::NeedsPositiveValue { span }),
|
||||||
0..=MAX => Ok(val as $type),
|
0..=MAX => Ok(val as $type),
|
||||||
|
@ -83,7 +83,7 @@ pub enum Value {
|
|||||||
internal_span: Span,
|
internal_span: Span,
|
||||||
},
|
},
|
||||||
Filesize {
|
Filesize {
|
||||||
val: i64,
|
val: Filesize,
|
||||||
// note: spans are being refactored out of Value
|
// note: spans are being refactored out of Value
|
||||||
// please use .span() instead of matching this span value
|
// please use .span() instead of matching this span value
|
||||||
#[serde(rename = "span")]
|
#[serde(rename = "span")]
|
||||||
@ -301,7 +301,7 @@ impl Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the inner `i64` filesize value or an error if this `Value` is not a filesize
|
/// Returns the inner `i64` filesize value or an error if this `Value` is not a filesize
|
||||||
pub fn as_filesize(&self) -> Result<i64, ShellError> {
|
pub fn as_filesize(&self) -> Result<Filesize, ShellError> {
|
||||||
if let Value::Filesize { val, .. } = self {
|
if let Value::Filesize { val, .. } = self {
|
||||||
Ok(*val)
|
Ok(*val)
|
||||||
} else {
|
} else {
|
||||||
@ -1819,9 +1819,9 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn filesize(val: i64, span: Span) -> Value {
|
pub fn filesize(val: impl Into<Filesize>, span: Span) -> Value {
|
||||||
Value::Filesize {
|
Value::Filesize {
|
||||||
val,
|
val: val.into(),
|
||||||
internal_span: span,
|
internal_span: span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1938,7 +1938,7 @@ impl Value {
|
|||||||
|
|
||||||
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
|
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
|
||||||
/// when used in errors.
|
/// when used in errors.
|
||||||
pub fn test_filesize(val: i64) -> Value {
|
pub fn test_filesize(val: impl Into<Filesize>) -> Value {
|
||||||
Value::filesize(val, Span::test_data())
|
Value::filesize(val, Span::test_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2478,7 +2478,7 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||||
if let Some(val) = lhs.checked_add(*rhs) {
|
if let Some(val) = *lhs + *rhs {
|
||||||
Ok(Value::filesize(val, span))
|
Ok(Value::filesize(val, span))
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::OperatorOverflow {
|
Err(ShellError::OperatorOverflow {
|
||||||
@ -2585,7 +2585,7 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||||
if let Some(val) = lhs.checked_sub(*rhs) {
|
if let Some(val) = *lhs - *rhs {
|
||||||
Ok(Value::filesize(val, span))
|
Ok(Value::filesize(val, span))
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::OperatorOverflow {
|
Err(ShellError::OperatorOverflow {
|
||||||
@ -2633,16 +2633,48 @@ impl Value {
|
|||||||
Ok(Value::float(lhs * rhs, span))
|
Ok(Value::float(lhs * rhs, span))
|
||||||
}
|
}
|
||||||
(Value::Int { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
(Value::Int { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||||
Ok(Value::filesize(*lhs * *rhs, span))
|
if let Some(val) = *lhs * *rhs {
|
||||||
|
Ok(Value::filesize(val, span))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::OperatorOverflow {
|
||||||
|
msg: "multiply operation overflowed".into(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||||
Ok(Value::filesize(*lhs * *rhs, span))
|
if let Some(val) = *lhs * *rhs {
|
||||||
|
Ok(Value::filesize(val, span))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::OperatorOverflow {
|
||||||
|
msg: "multiply operation overflowed".into(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(Value::Float { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
(Value::Float { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||||
Ok(Value::filesize((*lhs * *rhs as f64) as i64, span))
|
if let Some(val) = *lhs * *rhs {
|
||||||
|
Ok(Value::filesize(val, span))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::OperatorOverflow {
|
||||||
|
msg: "multiply operation overflowed".into(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||||
Ok(Value::filesize((*lhs as f64 * *rhs) as i64, span))
|
if let Some(val) = *lhs * *rhs {
|
||||||
|
Ok(Value::filesize(val, span))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::OperatorOverflow {
|
||||||
|
msg: "multiply operation overflowed".into(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(Value::Int { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
(Value::Int { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||||
Ok(Value::duration(*lhs * *rhs, span))
|
Ok(Value::duration(*lhs * *rhs, span))
|
||||||
@ -2700,14 +2732,14 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||||
if *rhs == 0 {
|
if *rhs == Filesize::ZERO {
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
} else {
|
} else {
|
||||||
Ok(Value::float(*lhs as f64 / *rhs as f64, span))
|
Ok(Value::float(lhs.get() as f64 / rhs.get() as f64, span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||||
if let Some(val) = lhs.checked_div(*rhs) {
|
if let Some(val) = lhs.get().checked_div(*rhs) {
|
||||||
Ok(Value::filesize(val, span))
|
Ok(Value::filesize(val, span))
|
||||||
} else if *rhs == 0 {
|
} else if *rhs == 0 {
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
@ -2721,9 +2753,8 @@ impl Value {
|
|||||||
}
|
}
|
||||||
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||||
if *rhs != 0.0 {
|
if *rhs != 0.0 {
|
||||||
let val = *lhs as f64 / rhs;
|
if let Ok(val) = Filesize::try_from(lhs.get() as f64 / rhs) {
|
||||||
if i64::MIN as f64 <= val && val <= i64::MAX as f64 {
|
Ok(Value::filesize(val, span))
|
||||||
Ok(Value::filesize(val as i64, span))
|
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::OperatorOverflow {
|
Err(ShellError::OperatorOverflow {
|
||||||
msg: "division operation overflowed".into(),
|
msg: "division operation overflowed".into(),
|
||||||
@ -2785,163 +2816,6 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn modulo(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
|
||||||
// Based off the unstable `div_floor` function in the std library.
|
|
||||||
fn checked_mod_i64(dividend: i64, divisor: i64) -> Option<i64> {
|
|
||||||
let remainder = dividend.checked_rem(divisor)?;
|
|
||||||
if (remainder > 0 && divisor < 0) || (remainder < 0 && divisor > 0) {
|
|
||||||
// Note that `remainder + divisor` cannot overflow, because `remainder` and
|
|
||||||
// `divisor` have opposite signs.
|
|
||||||
Some(remainder + divisor)
|
|
||||||
} else {
|
|
||||||
Some(remainder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn checked_mod_f64(dividend: f64, divisor: f64) -> Option<f64> {
|
|
||||||
if divisor == 0.0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let remainder = dividend % divisor;
|
|
||||||
if (remainder > 0.0 && divisor < 0.0) || (remainder < 0.0 && divisor > 0.0) {
|
|
||||||
Some(remainder + divisor)
|
|
||||||
} else {
|
|
||||||
Some(remainder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match (self, rhs) {
|
|
||||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
|
||||||
if let Some(val) = checked_mod_i64(*lhs, *rhs) {
|
|
||||||
Ok(Value::int(val, span))
|
|
||||||
} else if *rhs == 0 {
|
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
|
||||||
} else {
|
|
||||||
Err(ShellError::OperatorOverflow {
|
|
||||||
msg: "modulo operation overflowed".into(),
|
|
||||||
span,
|
|
||||||
help: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
|
||||||
if let Some(val) = checked_mod_f64(*lhs as f64, *rhs) {
|
|
||||||
Ok(Value::float(val, span))
|
|
||||||
} else {
|
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
|
||||||
if let Some(val) = checked_mod_f64(*lhs, *rhs as f64) {
|
|
||||||
Ok(Value::float(val, span))
|
|
||||||
} else {
|
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
|
||||||
if let Some(val) = checked_mod_f64(*lhs, *rhs) {
|
|
||||||
Ok(Value::float(val, span))
|
|
||||||
} else {
|
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
|
||||||
if let Some(val) = checked_mod_i64(*lhs, *rhs) {
|
|
||||||
Ok(Value::filesize(val, span))
|
|
||||||
} else if *rhs == 0 {
|
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
|
||||||
} else {
|
|
||||||
Err(ShellError::OperatorOverflow {
|
|
||||||
msg: "modulo operation overflowed".into(),
|
|
||||||
span,
|
|
||||||
help: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
|
||||||
if let Some(val) = checked_mod_i64(*lhs, *rhs) {
|
|
||||||
Ok(Value::filesize(val, span))
|
|
||||||
} else if *rhs == 0 {
|
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
|
||||||
} else {
|
|
||||||
Err(ShellError::OperatorOverflow {
|
|
||||||
msg: "modulo operation overflowed".into(),
|
|
||||||
span,
|
|
||||||
help: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
|
||||||
if let Some(val) = checked_mod_f64(*lhs as f64, *rhs) {
|
|
||||||
if i64::MIN as f64 <= val && val <= i64::MAX as f64 {
|
|
||||||
Ok(Value::filesize(val as i64, span))
|
|
||||||
} else {
|
|
||||||
Err(ShellError::OperatorOverflow {
|
|
||||||
msg: "modulo operation overflowed".into(),
|
|
||||||
span,
|
|
||||||
help: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
|
||||||
if let Some(val) = checked_mod_i64(*lhs, *rhs) {
|
|
||||||
Ok(Value::duration(val, span))
|
|
||||||
} else if *rhs == 0 {
|
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
|
||||||
} else {
|
|
||||||
Err(ShellError::OperatorOverflow {
|
|
||||||
msg: "division operation overflowed".into(),
|
|
||||||
span,
|
|
||||||
help: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
|
||||||
if let Some(val) = checked_mod_i64(*lhs, *rhs) {
|
|
||||||
Ok(Value::duration(val, span))
|
|
||||||
} else if *rhs == 0 {
|
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
|
||||||
} else {
|
|
||||||
Err(ShellError::OperatorOverflow {
|
|
||||||
msg: "division operation overflowed".into(),
|
|
||||||
span,
|
|
||||||
help: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
|
||||||
if let Some(val) = checked_mod_f64(*lhs as f64, *rhs) {
|
|
||||||
if i64::MIN as f64 <= val && val <= i64::MAX as f64 {
|
|
||||||
Ok(Value::duration(val as i64, span))
|
|
||||||
} else {
|
|
||||||
Err(ShellError::OperatorOverflow {
|
|
||||||
msg: "division operation overflowed".into(),
|
|
||||||
span,
|
|
||||||
help: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Value::Custom { val: lhs, .. }, rhs) => {
|
|
||||||
lhs.operation(span, Operator::Math(Math::Modulo), op, rhs)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => Err(ShellError::OperatorMismatch {
|
|
||||||
op_span: op,
|
|
||||||
lhs_ty: self.get_type().to_string(),
|
|
||||||
lhs_span: self.span(),
|
|
||||||
rhs_ty: rhs.get_type().to_string(),
|
|
||||||
rhs_span: rhs.span(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn floor_div(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
pub fn floor_div(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||||
// Taken from the unstable `div_floor` function in the std library.
|
// Taken from the unstable `div_floor` function in the std library.
|
||||||
fn checked_div_floor_i64(dividend: i64, divisor: i64) -> Option<i64> {
|
fn checked_div_floor_i64(dividend: i64, divisor: i64) -> Option<i64> {
|
||||||
@ -3003,9 +2877,9 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||||
if let Some(val) = checked_div_floor_i64(*lhs, *rhs) {
|
if let Some(val) = checked_div_floor_i64(lhs.get(), rhs.get()) {
|
||||||
Ok(Value::int(val, span))
|
Ok(Value::int(val, span))
|
||||||
} else if *rhs == 0 {
|
} else if *rhs == Filesize::ZERO {
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::OperatorOverflow {
|
Err(ShellError::OperatorOverflow {
|
||||||
@ -3016,7 +2890,7 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||||
if let Some(val) = checked_div_floor_i64(*lhs, *rhs) {
|
if let Some(val) = checked_div_floor_i64(lhs.get(), *rhs) {
|
||||||
Ok(Value::filesize(val, span))
|
Ok(Value::filesize(val, span))
|
||||||
} else if *rhs == 0 {
|
} else if *rhs == 0 {
|
||||||
Err(ShellError::DivisionByZero { span: op })
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
@ -3029,9 +2903,9 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||||
if let Some(val) = checked_div_floor_f64(*lhs as f64, *rhs) {
|
if let Some(val) = checked_div_floor_f64(lhs.get() as f64, *rhs) {
|
||||||
if i64::MIN as f64 <= val && val <= i64::MAX as f64 {
|
if let Ok(val) = Filesize::try_from(val) {
|
||||||
Ok(Value::filesize(val as i64, span))
|
Ok(Value::filesize(val, span))
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::OperatorOverflow {
|
Err(ShellError::OperatorOverflow {
|
||||||
msg: "division operation overflowed".into(),
|
msg: "division operation overflowed".into(),
|
||||||
@ -3097,6 +2971,163 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn modulo(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||||
|
// Based off the unstable `div_floor` function in the std library.
|
||||||
|
fn checked_mod_i64(dividend: i64, divisor: i64) -> Option<i64> {
|
||||||
|
let remainder = dividend.checked_rem(divisor)?;
|
||||||
|
if (remainder > 0 && divisor < 0) || (remainder < 0 && divisor > 0) {
|
||||||
|
// Note that `remainder + divisor` cannot overflow, because `remainder` and
|
||||||
|
// `divisor` have opposite signs.
|
||||||
|
Some(remainder + divisor)
|
||||||
|
} else {
|
||||||
|
Some(remainder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checked_mod_f64(dividend: f64, divisor: f64) -> Option<f64> {
|
||||||
|
if divisor == 0.0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let remainder = dividend % divisor;
|
||||||
|
if (remainder > 0.0 && divisor < 0.0) || (remainder < 0.0 && divisor > 0.0) {
|
||||||
|
Some(remainder + divisor)
|
||||||
|
} else {
|
||||||
|
Some(remainder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match (self, rhs) {
|
||||||
|
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||||
|
if let Some(val) = checked_mod_i64(*lhs, *rhs) {
|
||||||
|
Ok(Value::int(val, span))
|
||||||
|
} else if *rhs == 0 {
|
||||||
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
|
} else {
|
||||||
|
Err(ShellError::OperatorOverflow {
|
||||||
|
msg: "modulo operation overflowed".into(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||||
|
if let Some(val) = checked_mod_f64(*lhs as f64, *rhs) {
|
||||||
|
Ok(Value::float(val, span))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||||
|
if let Some(val) = checked_mod_f64(*lhs, *rhs as f64) {
|
||||||
|
Ok(Value::float(val, span))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||||
|
if let Some(val) = checked_mod_f64(*lhs, *rhs) {
|
||||||
|
Ok(Value::float(val, span))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||||
|
if let Some(val) = checked_mod_i64(lhs.get(), rhs.get()) {
|
||||||
|
Ok(Value::filesize(val, span))
|
||||||
|
} else if *rhs == Filesize::ZERO {
|
||||||
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
|
} else {
|
||||||
|
Err(ShellError::OperatorOverflow {
|
||||||
|
msg: "modulo operation overflowed".into(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||||
|
if let Some(val) = checked_mod_i64(lhs.get(), *rhs) {
|
||||||
|
Ok(Value::filesize(val, span))
|
||||||
|
} else if *rhs == 0 {
|
||||||
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
|
} else {
|
||||||
|
Err(ShellError::OperatorOverflow {
|
||||||
|
msg: "modulo operation overflowed".into(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||||
|
if let Some(val) = checked_mod_f64(lhs.get() as f64, *rhs) {
|
||||||
|
if let Ok(val) = Filesize::try_from(val) {
|
||||||
|
Ok(Value::filesize(val, span))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::OperatorOverflow {
|
||||||
|
msg: "modulo operation overflowed".into(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||||
|
if let Some(val) = checked_mod_i64(*lhs, *rhs) {
|
||||||
|
Ok(Value::duration(val, span))
|
||||||
|
} else if *rhs == 0 {
|
||||||
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
|
} else {
|
||||||
|
Err(ShellError::OperatorOverflow {
|
||||||
|
msg: "division operation overflowed".into(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||||
|
if let Some(val) = checked_mod_i64(*lhs, *rhs) {
|
||||||
|
Ok(Value::duration(val, span))
|
||||||
|
} else if *rhs == 0 {
|
||||||
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
|
} else {
|
||||||
|
Err(ShellError::OperatorOverflow {
|
||||||
|
msg: "division operation overflowed".into(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||||
|
if let Some(val) = checked_mod_f64(*lhs as f64, *rhs) {
|
||||||
|
if i64::MIN as f64 <= val && val <= i64::MAX as f64 {
|
||||||
|
Ok(Value::duration(val as i64, span))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::OperatorOverflow {
|
||||||
|
msg: "division operation overflowed".into(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ShellError::DivisionByZero { span: op })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Value::Custom { val: lhs, .. }, rhs) => {
|
||||||
|
lhs.operation(span, Operator::Math(Math::Modulo), op, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => Err(ShellError::OperatorMismatch {
|
||||||
|
op_span: op,
|
||||||
|
lhs_ty: self.get_type().to_string(),
|
||||||
|
lhs_span: self.span(),
|
||||||
|
rhs_ty: rhs.get_type().to_string(),
|
||||||
|
rhs_span: rhs.span(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn lt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
pub fn lt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||||
if let (Value::Custom { val: lhs, .. }, rhs) = (self, rhs) {
|
if let (Value::Custom { val: lhs, .. }, rhs) = (self, rhs) {
|
||||||
return lhs.operation(
|
return lhs.operation(
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
use std::time::SystemTime;
|
use crate::FormatCmdsPlugin;
|
||||||
|
|
||||||
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
|
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
|
||||||
use nu_protocol::{Category, Example, LabeledError, Record, Signature, Span, Value as NuValue};
|
use nu_protocol::{Category, Example, LabeledError, Record, Signature, Span, Value as NuValue};
|
||||||
use plist::{Integer, Value as PlistValue};
|
use plist::Value as PlistValue;
|
||||||
|
use std::time::SystemTime;
|
||||||
use crate::FormatCmdsPlugin;
|
|
||||||
|
|
||||||
pub(crate) struct IntoPlist;
|
pub(crate) struct IntoPlist;
|
||||||
|
|
||||||
@ -68,7 +66,7 @@ fn convert_nu_value(nu_val: &NuValue) -> Result<PlistValue, LabeledError> {
|
|||||||
NuValue::String { val, .. } => Ok(PlistValue::String(val.to_owned())),
|
NuValue::String { val, .. } => Ok(PlistValue::String(val.to_owned())),
|
||||||
NuValue::Bool { val, .. } => Ok(PlistValue::Boolean(*val)),
|
NuValue::Bool { val, .. } => Ok(PlistValue::Boolean(*val)),
|
||||||
NuValue::Float { val, .. } => Ok(PlistValue::Real(*val)),
|
NuValue::Float { val, .. } => Ok(PlistValue::Real(*val)),
|
||||||
NuValue::Int { val, .. } => Ok(PlistValue::Integer(Into::<Integer>::into(*val))),
|
NuValue::Int { val, .. } => Ok(PlistValue::Integer((*val).into())),
|
||||||
NuValue::Binary { val, .. } => Ok(PlistValue::Data(val.to_owned())),
|
NuValue::Binary { val, .. } => Ok(PlistValue::Data(val.to_owned())),
|
||||||
NuValue::Record { val, .. } => convert_nu_dict(val),
|
NuValue::Record { val, .. } => convert_nu_dict(val),
|
||||||
NuValue::List { vals, .. } => Ok(PlistValue::Array(
|
NuValue::List { vals, .. } => Ok(PlistValue::Array(
|
||||||
@ -77,7 +75,7 @@ fn convert_nu_value(nu_val: &NuValue) -> Result<PlistValue, LabeledError> {
|
|||||||
.collect::<Result<_, _>>()?,
|
.collect::<Result<_, _>>()?,
|
||||||
)),
|
)),
|
||||||
NuValue::Date { val, .. } => Ok(PlistValue::Date(SystemTime::from(val.to_owned()).into())),
|
NuValue::Date { val, .. } => Ok(PlistValue::Date(SystemTime::from(val.to_owned()).into())),
|
||||||
NuValue::Filesize { val, .. } => Ok(PlistValue::Integer(Into::<Integer>::into(*val))),
|
NuValue::Filesize { val, .. } => Ok(PlistValue::Integer(val.get().into())),
|
||||||
_ => Err(build_label_error(
|
_ => Err(build_label_error(
|
||||||
format!("{:?} is not convertible", nu_val),
|
format!("{:?} is not convertible", nu_val),
|
||||||
span,
|
span,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Expr, Expression, ListItem, RecordItem},
|
ast::{Expr, Expression, ListItem, RecordItem},
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
Range, Record, ShellError, Span, Type, Unit, Value,
|
Filesize, IntoValue, Range, Record, ShellError, Span, Type, Unit, Value,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -407,32 +407,15 @@ fn convert_to_value(
|
|||||||
};
|
};
|
||||||
|
|
||||||
match value.unit.item {
|
match value.unit.item {
|
||||||
Unit::Byte => Ok(Value::filesize(size, span)),
|
Unit::Filesize(unit) => match Filesize::from_unit(size, unit) {
|
||||||
Unit::Kilobyte => Ok(Value::filesize(size * 1000, span)),
|
Some(val) => Ok(val.into_value(span)),
|
||||||
Unit::Megabyte => Ok(Value::filesize(size * 1000 * 1000, span)),
|
None => Err(ShellError::OutsideSpannedLabeledError {
|
||||||
Unit::Gigabyte => Ok(Value::filesize(size * 1000 * 1000 * 1000, span)),
|
src: original_text.into(),
|
||||||
Unit::Terabyte => Ok(Value::filesize(size * 1000 * 1000 * 1000 * 1000, span)),
|
error: "filesize too large".into(),
|
||||||
Unit::Petabyte => Ok(Value::filesize(
|
msg: "filesize too large".into(),
|
||||||
size * 1000 * 1000 * 1000 * 1000 * 1000,
|
span: expr.span,
|
||||||
span,
|
}),
|
||||||
)),
|
},
|
||||||
Unit::Exabyte => Ok(Value::filesize(
|
|
||||||
size * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
|
|
||||||
span,
|
|
||||||
)),
|
|
||||||
|
|
||||||
Unit::Kibibyte => Ok(Value::filesize(size * 1024, span)),
|
|
||||||
Unit::Mebibyte => Ok(Value::filesize(size * 1024 * 1024, span)),
|
|
||||||
Unit::Gibibyte => Ok(Value::filesize(size * 1024 * 1024 * 1024, span)),
|
|
||||||
Unit::Tebibyte => Ok(Value::filesize(size * 1024 * 1024 * 1024 * 1024, span)),
|
|
||||||
Unit::Pebibyte => Ok(Value::filesize(
|
|
||||||
size * 1024 * 1024 * 1024 * 1024 * 1024,
|
|
||||||
span,
|
|
||||||
)),
|
|
||||||
Unit::Exbibyte => Ok(Value::filesize(
|
|
||||||
size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
|
|
||||||
span,
|
|
||||||
)),
|
|
||||||
|
|
||||||
Unit::Nanosecond => Ok(Value::duration(size, span)),
|
Unit::Nanosecond => Ok(Value::duration(size, span)),
|
||||||
Unit::Microsecond => Ok(Value::duration(size * 1000, span)),
|
Unit::Microsecond => Ok(Value::duration(size * 1000, span)),
|
||||||
|
@ -155,7 +155,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn filesize() {
|
fn filesize() {
|
||||||
nuon_end_to_end("1024b", Some(Value::test_filesize(1024)));
|
nuon_end_to_end("1024b", Some(Value::test_filesize(1024)));
|
||||||
assert_eq!(from_nuon("1kib", None).unwrap(), Value::test_filesize(1024),);
|
assert_eq!(from_nuon("1kib", None).unwrap(), Value::test_filesize(1024));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -110,7 +110,7 @@ fn value_to_string(
|
|||||||
// Propagate existing errors
|
// Propagate existing errors
|
||||||
Value::Error { error, .. } => Err(*error.clone()),
|
Value::Error { error, .. } => Err(*error.clone()),
|
||||||
// FIXME: make filesizes use the shortest lossless representation.
|
// FIXME: make filesizes use the shortest lossless representation.
|
||||||
Value::Filesize { val, .. } => Ok(format!("{}b", *val)),
|
Value::Filesize { val, .. } => Ok(format!("{}b", val.get())),
|
||||||
Value::Float { val, .. } => {
|
Value::Float { val, .. } => {
|
||||||
// This serialises these as 'nan', 'inf' and '-inf', respectively.
|
// This serialises these as 'nan', 'inf' and '-inf', respectively.
|
||||||
if &val.round() == val && val.is_finite() {
|
if &val.round() == val && val.is_finite() {
|
||||||
|
Loading…
Reference in New Issue
Block a user