forked from extern/nushell
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:
@ -1,5 +1,5 @@
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use nu_protocol::{ShellError, Span, Value};
|
||||
use nu_protocol::{Filesize, ShellError, Span, Value};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
/// A subset of [`Value`], which is hashable.
|
||||
@ -30,7 +30,7 @@ pub enum HashableValue {
|
||||
span: Span,
|
||||
},
|
||||
Filesize {
|
||||
val: i64,
|
||||
val: Filesize,
|
||||
span: Span,
|
||||
},
|
||||
Duration {
|
||||
@ -198,7 +198,10 @@ mod test {
|
||||
(Value::int(1, span), HashableValue::Int { val: 1, span }),
|
||||
(
|
||||
Value::filesize(1, span),
|
||||
HashableValue::Filesize { val: 1, span },
|
||||
HashableValue::Filesize {
|
||||
val: 1.into(),
|
||||
span,
|
||||
},
|
||||
),
|
||||
(
|
||||
Value::duration(1, span),
|
||||
|
@ -167,7 +167,7 @@ fn fill(
|
||||
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
match input {
|
||||
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::String { val, .. } => fill_string(val, args, span),
|
||||
// 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::Int { 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::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),
|
||||
|
@ -253,7 +253,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
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(
|
||||
{
|
||||
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::Int { 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::Date { 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();
|
||||
Ok(match v {
|
||||
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::Date { val, .. } => nu_json::Value::String(val.to_string()),
|
||||
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)?;
|
||||
}
|
||||
Value::Filesize { val, .. } => {
|
||||
mp::write_sint(out, *val).err_span(span)?;
|
||||
mp::write_sint(out, val.get()).err_span(span)?;
|
||||
}
|
||||
Value::Duration { val, .. } => {
|
||||
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 {
|
||||
Value::Bool { val, .. } => toml::Value::Boolean(*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::Date { val, .. } => toml::Value::Datetime(to_toml_datetime(val)),
|
||||
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 {
|
||||
Value::Bool { val, .. } => serde_yaml::Value::Bool(*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::Date { val, .. } => serde_yaml::Value::String(val.to_string()),
|
||||
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 span = total.span();
|
||||
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)),
|
||||
_ => 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, .. } => {
|
||||
Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Float))
|
||||
}
|
||||
Value::Filesize { val, .. } => {
|
||||
Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Filesize))
|
||||
}
|
||||
Value::Filesize { val, .. } => Ok(HashableType::new(
|
||||
val.get().to_ne_bytes(),
|
||||
NumberTypes::Filesize,
|
||||
)),
|
||||
Value::Error { error, .. } => Err(*error.clone()),
|
||||
other => Err(ShellError::UnsupportedInput {
|
||||
msg: "Unable to give a result with this input".to_string(),
|
||||
|
@ -1,5 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
use nu_protocol::format_filesize_from_conf;
|
||||
use rand::{thread_rng, RngCore};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -37,7 +37,27 @@ impl Command for SubCommand {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> 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 out = vec![0u8; length];
|
||||
|
@ -1,5 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
use nu_protocol::format_filesize_from_conf;
|
||||
use rand::{
|
||||
distributions::{Alphanumeric, Distribution},
|
||||
thread_rng,
|
||||
@ -73,14 +73,36 @@ fn chars(
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
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 random_string = Alphanumeric
|
||||
.sample_iter(&mut rng)
|
||||
.take(chars_length)
|
||||
.take(length)
|
||||
.map(char::from)
|
||||
.collect::<String>();
|
||||
|
||||
|
Reference in New Issue
Block a user