small refactoring around units and add tests (#15746)

Closes #14469

# Description
- ~~Implement the ``--unit`` conversion in "into int" command~~
- New ``ShellError::InvalidUnit`` unit if users enter wrong units
- Made ``ShellError::CantConvertToDuration`` more generic: became
``CantConvertToUnit``
- Tried to improve the way we parse units and get the supported units.
It's not complete, though, I will continue this refactoring in another
PR. But I already did some small refactorings in the "format duration"
and "format filesize" commands
- Add tests for "format filesize" and "format duration"

# User-Facing Changes

```nu
~> 1MB | format filesize sec
Error: nu:🐚:invalid_unit

  × Invalid unit
   ╭─[entry #7:1:23]
 1 │ 1MB | format filesize sec
   ·                       ─┬─
   ·                        ╰── encountered here
   ╰────
  help: Supported units are: B, kB, MB, GB, TB, PB, EB, KiB, MiB, GiB, TiB, PiB, EiB

```
This commit is contained in:
Loïc Riegel 2025-05-17 00:41:26 +02:00 committed by GitHub
parent 70ba5d9d68
commit 58a8f30a25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 258 additions and 107 deletions

View File

@ -1,7 +1,9 @@
use std::str::FromStr;
use nu_cmd_base::input_handler::{CmdArgument, operate}; use nu_cmd_base::input_handler::{CmdArgument, operate};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_parser::{DURATION_UNIT_GROUPS, parse_unit_value}; use nu_parser::{DURATION_UNIT_GROUPS, parse_unit_value};
use nu_protocol::{Unit, ast::Expr}; use nu_protocol::{SUPPORTED_DURATION_UNITS, Unit, ast::Expr};
const NS_PER_US: i64 = 1_000; const NS_PER_US: i64 = 1_000;
const NS_PER_MS: i64 = 1_000_000; const NS_PER_MS: i64 = 1_000_000;
@ -26,7 +28,7 @@ const ALLOWED_SIGNS: [&str; 2] = ["+", "-"];
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct Arguments { struct Arguments {
unit: Option<Spanned<String>>, unit: Option<Spanned<Unit>>,
cell_paths: Option<Vec<CellPath>>, cell_paths: Option<Vec<CellPath>>,
} }
@ -95,28 +97,27 @@ impl Command for IntoDuration {
let cell_paths = call.rest(engine_state, stack, 0)?; let cell_paths = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let span = match input.span() {
Some(t) => t,
None => call.head,
};
let unit = match call.get_flag::<Spanned<String>>(engine_state, stack, "unit")? { let unit = match call.get_flag::<Spanned<String>>(engine_state, stack, "unit")? {
Some(spanned_unit) => { Some(spanned_unit) => match Unit::from_str(&spanned_unit.item) {
if ["ns", "us", "µs", "ms", "sec", "min", "hr", "day", "wk"] Ok(u) => match u {
.contains(&spanned_unit.item.as_str()) Unit::Filesize(_) => {
{ return Err(ShellError::InvalidUnit {
Some(spanned_unit) span: spanned_unit.span,
} else { supported_units: SUPPORTED_DURATION_UNITS.join(", "),
return Err(ShellError::CantConvertToDuration { });
details: spanned_unit.item, }
dst_span: span, _ => Some(Spanned {
src_span: span, item: u,
help: Some( span: spanned_unit.span,
"supported units are ns, us/µs, ms, sec, min, hr, day, and wk" }),
.to_string(), },
), Err(_) => {
return Err(ShellError::InvalidUnit {
span: spanned_unit.span,
supported_units: SUPPORTED_DURATION_UNITS.join(", "),
}); });
} }
} },
None => None, None => None,
}; };
let args = Arguments { unit, cell_paths }; let args = Arguments { unit, cell_paths };
@ -244,11 +245,9 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
} }
} }
Err(ShellError::CantConvertToDuration { Err(ShellError::InvalidUnit {
details: s.to_string(), span,
dst_span: span, supported_units: SUPPORTED_DURATION_UNITS.join(", "),
src_span: span,
help: Some("supported units are ns, us/µs, ms, sec, min, hr, day, and wk".to_string()),
}) })
} }
@ -270,9 +269,9 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
} }
} }
let unit: &str = match unit_option { let unit = match unit_option {
Some(unit) => &unit.item, Some(unit) => &unit.item,
None => "ns", None => &Unit::Nanosecond,
}; };
match input { match input {
@ -417,16 +416,16 @@ fn parse_number_from_record(col_val: &Value, head: &Span) -> Result<i64, ShellEr
Ok(value) Ok(value)
} }
fn unit_to_ns_factor(unit: &str) -> i64 { fn unit_to_ns_factor(unit: &Unit) -> i64 {
match unit { match unit {
"ns" => 1, Unit::Nanosecond => 1,
"us" | "µs" => NS_PER_US, Unit::Microsecond => NS_PER_US,
"ms" => NS_PER_MS, Unit::Millisecond => NS_PER_MS,
"sec" => NS_PER_SEC, Unit::Second => NS_PER_SEC,
"min" => NS_PER_MINUTE, Unit::Minute => NS_PER_MINUTE,
"hr" => NS_PER_HOUR, Unit::Hour => NS_PER_HOUR,
"day" => NS_PER_DAY, Unit::Day => NS_PER_DAY,
"wk" => NS_PER_WEEK, Unit::Week => NS_PER_WEEK,
_ => 0, _ => 0,
} }
} }
@ -462,7 +461,7 @@ mod test {
fn turns_string_to_duration(#[case] phrase: &str, #[case] expected_duration_val: i64) { fn turns_string_to_duration(#[case] phrase: &str, #[case] expected_duration_val: i64) {
let args = Arguments { let args = Arguments {
unit: Some(Spanned { unit: Some(Spanned {
item: "ns".to_string(), item: Unit::Nanosecond,
span: Span::test_data(), span: Span::test_data(),
}), }),
cell_paths: None, cell_paths: None,

View File

@ -240,58 +240,59 @@ impl Command for IntoInt {
} }
} }
fn action(input: &Value, args: &Arguments, span: Span) -> Value { fn action(input: &Value, args: &Arguments, head: Span) -> Value {
let radix = args.radix; let radix = args.radix;
let signed = args.signed; let signed = args.signed;
let little_endian = args.little_endian; let little_endian = args.little_endian;
let val_span = input.span(); let val_span = input.span();
match input { match input {
Value::Int { val: _, .. } => { Value::Int { val: _, .. } => {
if radix == 10 { if radix == 10 {
input.clone() input.clone()
} else { } else {
convert_int(input, span, radix) convert_int(input, head, radix)
} }
} }
Value::Filesize { val, .. } => Value::int(val.get(), span), Value::Filesize { val, .. } => Value::int(val.get(), head),
Value::Float { val, .. } => Value::int( Value::Float { val, .. } => Value::int(
{ {
if radix == 10 { if radix == 10 {
*val as i64 *val as i64
} else { } else {
match convert_int(&Value::int(*val as i64, span), span, radix).as_int() { match convert_int(&Value::int(*val as i64, head), head, radix).as_int() {
Ok(v) => v, Ok(v) => v,
_ => { _ => {
return Value::error( return Value::error(
ShellError::CantConvert { ShellError::CantConvert {
to_type: "float".to_string(), to_type: "float".to_string(),
from_type: "int".to_string(), from_type: "int".to_string(),
span, span: head,
help: None, help: None,
}, },
span, head,
); );
} }
} }
} }
}, },
span, head,
), ),
Value::String { val, .. } => { Value::String { val, .. } => {
if radix == 10 { if radix == 10 {
match int_from_string(val, span) { match int_from_string(val, head) {
Ok(val) => Value::int(val, span), Ok(val) => Value::int(val, head),
Err(error) => Value::error(error, span), Err(error) => Value::error(error, head),
} }
} else { } else {
convert_int(input, span, radix) convert_int(input, head, radix)
} }
} }
Value::Bool { val, .. } => { Value::Bool { val, .. } => {
if *val { if *val {
Value::int(1, span) Value::int(1, head)
} else { } else {
Value::int(0, span) Value::int(0, head)
} }
} }
Value::Date { val, .. } => { Value::Date { val, .. } => {
@ -310,15 +311,15 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
ShellError::IncorrectValue { ShellError::IncorrectValue {
msg: "DateTime out of range for timestamp: 1677-09-21T00:12:43Z to 2262-04-11T23:47:16".to_string(), msg: "DateTime out of range for timestamp: 1677-09-21T00:12:43Z to 2262-04-11T23:47:16".to_string(),
val_span, val_span,
call_span: span, call_span: head,
}, },
span, head,
) )
} else { } else {
Value::int(val.timestamp_nanos_opt().unwrap_or_default(), span) Value::int(val.timestamp_nanos_opt().unwrap_or_default(), head)
} }
} }
Value::Duration { val, .. } => Value::int(*val, span), Value::Duration { val, .. } => Value::int(*val, head),
Value::Binary { val, .. } => { Value::Binary { val, .. } => {
use byteorder::{BigEndian, ByteOrder, LittleEndian}; use byteorder::{BigEndian, ByteOrder, LittleEndian};
@ -326,7 +327,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
let size = val.len(); let size = val.len();
if size == 0 { if size == 0 {
return Value::int(0, span); return Value::int(0, head);
} }
if size > 8 { if size > 8 {
@ -334,22 +335,22 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
ShellError::IncorrectValue { ShellError::IncorrectValue {
msg: format!("binary input is too large to convert to int ({size} bytes)"), msg: format!("binary input is too large to convert to int ({size} bytes)"),
val_span, val_span,
call_span: span, call_span: head,
}, },
span, head,
); );
} }
match (little_endian, signed) { match (little_endian, signed) {
(true, true) => Value::int(LittleEndian::read_int(&val, size), span), (true, true) => Value::int(LittleEndian::read_int(&val, size), head),
(false, true) => Value::int(BigEndian::read_int(&val, size), span), (false, true) => Value::int(BigEndian::read_int(&val, size), head),
(true, false) => { (true, false) => {
while val.len() < 8 { while val.len() < 8 {
val.push(0); val.push(0);
} }
val.resize(8, 0); val.resize(8, 0);
Value::int(LittleEndian::read_i64(&val), span) Value::int(LittleEndian::read_i64(&val), head)
} }
(false, false) => { (false, false) => {
while val.len() < 8 { while val.len() < 8 {
@ -357,7 +358,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
} }
val.resize(8, 0); val.resize(8, 0);
Value::int(BigEndian::read_i64(&val), span) Value::int(BigEndian::read_i64(&val), head)
} }
} }
} }
@ -368,10 +369,10 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
exp_input_type: "int, float, filesize, date, string, binary, duration, or bool" exp_input_type: "int, float, filesize, date, string, binary, duration, or bool"
.into(), .into(),
wrong_type: other.get_type().to_string(), wrong_type: other.get_type().to_string(),
dst_span: span, dst_span: head,
src_span: other.span(), src_span: other.span(),
}, },
span, head,
), ),
} }
} }

View File

@ -1,8 +1,9 @@
use nu_cmd_base::input_handler::{CmdArgument, operate}; use nu_cmd_base::input_handler::{CmdArgument, operate};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::SUPPORTED_DURATION_UNITS;
struct Arguments { struct Arguments {
format_value: String, format_value: Spanned<String>,
float_precision: usize, float_precision: usize,
cell_paths: Option<Vec<CellPath>>, cell_paths: Option<Vec<CellPath>>,
} }
@ -64,10 +65,12 @@ impl Command for FormatDuration {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let format_value = call let format_value = call.req::<Value>(engine_state, stack, 0)?;
.req::<Value>(engine_state, stack, 0)? let format_value_span = format_value.span();
.coerce_into_string()? let format_value = Spanned {
.to_ascii_lowercase(); item: format_value.coerce_into_string()?.to_ascii_lowercase(),
span: format_value_span,
};
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let float_precision = engine_state.config.float_precision as usize; let float_precision = engine_state.config.float_precision as usize;
@ -91,10 +94,12 @@ impl Command for FormatDuration {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let format_value = call let format_value = call.req_const::<Value>(working_set, 0)?;
.req_const::<Value>(working_set, 0)? let format_value_span = format_value.span();
.coerce_into_string()? let format_value = Spanned {
.to_ascii_lowercase(); item: format_value.coerce_into_string()?.to_ascii_lowercase(),
span: format_value_span,
};
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?; let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let float_precision = working_set.permanent().config.float_precision as usize; let float_precision = working_set.permanent().config.float_precision as usize;
@ -142,12 +147,12 @@ fn format_value_impl(val: &Value, arg: &Arguments, span: Span) -> Value {
Value::Duration { val: inner, .. } => { Value::Duration { val: inner, .. } => {
let duration = *inner; let duration = *inner;
let float_precision = arg.float_precision; let float_precision = arg.float_precision;
match convert_inner_to_unit(duration, &arg.format_value, span, inner_span) { match convert_inner_to_unit(duration, &arg.format_value.item, arg.format_value.span) {
Ok(d) => { Ok(d) => {
let unit = if &arg.format_value == "us" { let unit = if &arg.format_value.item == "us" {
"µs" "µs"
} else { } else {
&arg.format_value &arg.format_value.item
}; };
if d.fract() == 0.0 { if d.fract() == 0.0 {
Value::string(format!("{} {}", d, unit), inner_span) Value::string(format!("{} {}", d, unit), inner_span)
@ -171,12 +176,7 @@ fn format_value_impl(val: &Value, arg: &Arguments, span: Span) -> Value {
} }
} }
fn convert_inner_to_unit( fn convert_inner_to_unit(val: i64, to_unit: &str, span: Span) -> Result<f64, ShellError> {
val: i64,
to_unit: &str,
span: Span,
value_span: Span,
) -> Result<f64, ShellError> {
match to_unit { match to_unit {
"ns" => Ok(val as f64), "ns" => Ok(val as f64),
"us" => Ok(val as f64 / 1000.0), "us" => Ok(val as f64 / 1000.0),
@ -192,17 +192,13 @@ fn convert_inner_to_unit(
"yr" => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0), "yr" => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
"dec" => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0), "dec" => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
_ => Err(ShellError::CantConvertToDuration { _ => Err(ShellError::InvalidUnit {
details: to_unit.to_string(), span,
dst_span: span, supported_units: SUPPORTED_DURATION_UNITS.join(", "),
src_span: value_span,
help: Some(
"supported units are ns, us/µs, ms, sec, min, hr, day, wk, month, yr, and dec"
.to_string(),
),
}), }),
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,6 +1,8 @@
use nu_cmd_base::input_handler::{CmdArgument, operate}; use nu_cmd_base::input_handler::{CmdArgument, operate};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::{FilesizeFormatter, FilesizeUnit, engine::StateWorkingSet}; use nu_protocol::{
FilesizeFormatter, FilesizeUnit, SUPPORTED_FILESIZE_UNITS, engine::StateWorkingSet,
};
struct Arguments { struct Arguments {
unit: FilesizeUnit, unit: FilesizeUnit,
@ -115,11 +117,8 @@ impl Command for FormatFilesize {
} }
fn parse_filesize_unit(format: Spanned<String>) -> Result<FilesizeUnit, ShellError> { fn parse_filesize_unit(format: Spanned<String>) -> Result<FilesizeUnit, ShellError> {
format.item.parse().map_err(|_| ShellError::InvalidValue { format.item.parse().map_err(|_| ShellError::InvalidUnit {
valid: supported_units: SUPPORTED_FILESIZE_UNITS.join(", "),
"'B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', or 'EiB'"
.into(),
actual: format.item,
span: format.span, span: format.span,
}) })
} }

View File

@ -112,3 +112,17 @@ fn format_filesize_works_with_nonempty_files() {
}, },
) )
} }
#[test]
fn format_filesize_with_invalid_unit() {
let actual = nu!("1MB | format filesize sec");
assert!(actual.err.contains("invalid_unit"));
}
#[test]
fn format_duration_with_invalid_unit() {
let actual = nu!("1sec | format duration MB");
assert!(actual.err.contains("invalid_unit"));
}

View File

@ -95,6 +95,20 @@ fn into_duration_from_record_fails_with_invalid_sign() {
// Tests invalid usage // Tests invalid usage
#[test]
fn into_duration_invalid_unit() {
let actual = nu!(r#"1 | into duration --unit xx"#);
assert!(actual.err.contains("nu::shell::invalid_unit"));
}
#[test]
fn into_duration_filesize_unit() {
let actual = nu!(r#"1 | into duration --unit MB"#);
assert!(actual.err.contains("nu::shell::invalid_unit"));
}
#[test] #[test]
fn into_duration_from_record_fails_with_unknown_key() { fn into_duration_from_record_fails_with_unknown_key() {
let actual = nu!(r#"{week: 10, unknown: 1} | into duration"#); let actual = nu!(r#"{week: 10, unknown: 1} | into duration"#);

View File

@ -1,3 +1,4 @@
mod commands; mod commands;
mod format_conversions; mod format_conversions;
mod sort_utils; mod sort_utils;
mod string;

View File

@ -0,0 +1,15 @@
use nu_test_support::nu;
#[test]
fn format_duration() {
let actual = nu!(r#"1hr | format duration sec"#);
assert_eq!("3600 sec", actual.out);
}
#[test]
fn format_duration_with_invalid_unit() {
let actual = nu!(r#"1hr | format duration MB"#);
assert!(actual.err.contains("invalid_unit"));
}

View File

@ -0,0 +1,15 @@
use nu_test_support::nu;
#[test]
fn format_duration() {
let actual = nu!(r#"1MB | format filesize kB"#);
assert_eq!("1000 kB", actual.out);
}
#[test]
fn format_duration_with_invalid_unit() {
let actual = nu!(r#"1MB | format filesize sec"#);
assert!(actual.err.contains("invalid_unit"));
}

View File

@ -0,0 +1,2 @@
mod duration;
mod filesize;

View File

@ -0,0 +1 @@
mod format;

View File

@ -13,7 +13,7 @@ mod pipeline;
mod range; mod range;
mod table; mod table;
mod traverse; mod traverse;
mod unit; pub mod unit;
mod value_with_unit; mod value_with_unit;
pub use attribute::*; pub use attribute::*;

View File

@ -1,5 +1,24 @@
use crate::{Filesize, FilesizeUnit, IntoValue, ShellError, Span, Value}; use crate::{Filesize, FilesizeUnit, IntoValue, ShellError, Span, Value};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
use thiserror::Error;
pub const SUPPORTED_DURATION_UNITS: [&str; 9] =
["ns", "us", "µs", "ms", "sec", "min", "hr", "day", "wk"];
/// The error returned when failing to parse a [`Unit`].
///
/// This occurs when the string being parsed does not exactly match the name of one of the
/// enum cases in [`Unit`].
#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)]
pub struct ParseUnitError(());
impl fmt::Display for ParseUnitError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "invalid file size or duration unit")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Unit { pub enum Unit {
@ -78,4 +97,53 @@ impl Unit {
}, },
} }
} }
/// Returns the symbol [`str`] for a [`Unit`].
///
/// The returned string is the same exact string needed for a successful call to
/// [`parse`](str::parse) for a [`Unit`].
///
/// # Examples
/// ```
/// # use nu_protocol::{Unit, FilesizeUnit};
/// assert_eq!(Unit::Nanosecond.as_str(), "ns");
/// assert_eq!(Unit::Filesize(FilesizeUnit::B).as_str(), "B");
/// assert_eq!(Unit::Second.as_str().parse(), Ok(Unit::Second));
/// assert_eq!(Unit::Filesize(FilesizeUnit::KB).as_str().parse(), Ok(Unit::Filesize(FilesizeUnit::KB)));
/// ```
pub const fn as_str(&self) -> &'static str {
match self {
Unit::Filesize(u) => u.as_str(),
Unit::Nanosecond => "ns",
Unit::Microsecond => "us",
Unit::Millisecond => "ms",
Unit::Second => "sec",
Unit::Minute => "min",
Unit::Hour => "hr",
Unit::Day => "day",
Unit::Week => "wk",
}
}
}
impl FromStr for Unit {
type Err = ParseUnitError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(filesize_unit) = FilesizeUnit::from_str(s) {
return Ok(Unit::Filesize(filesize_unit));
};
match s {
"ns" => Ok(Unit::Nanosecond),
"us" | "µs" => Ok(Unit::Microsecond),
"ms" => Ok(Unit::Millisecond),
"sec" => Ok(Unit::Second),
"min" => Ok(Unit::Minute),
"hr" => Ok(Unit::Hour),
"day" => Ok(Unit::Day),
"wk" => Ok(Unit::Week),
_ => Err(ParseUnitError(())),
}
}
} }

View File

@ -76,7 +76,7 @@ pub enum ShellError {
exp_input_type: String, exp_input_type: String,
#[label("expected: {exp_input_type}")] #[label("expected: {exp_input_type}")]
dst_span: Span, dst_span: Span,
#[label("value originates from here")] #[label("value originates here")]
src_span: Span, src_span: Span,
}, },
@ -431,14 +431,20 @@ pub enum ShellError {
help: Option<String>, help: Option<String>,
}, },
#[error("Can't convert string `{details}` to duration.")] /// Failed to convert a value of one type into a different type by specifying a unit.
#[diagnostic(code(nu::shell::cant_convert_with_value))] ///
CantConvertToDuration { /// ## Resolution
details: String, ///
#[label("can't be converted to duration")] /// Check that the provided value can be converted in the provided: only Durations can be converted to duration units, and only Filesize can be converted to filesize units.
dst_span: Span, #[error("Can't convert {from_type} to the specified unit.")]
#[label("this string value...")] #[diagnostic(code(nu::shell::cant_convert_value_to_unit))]
src_span: Span, CantConvertToUnit {
to_type: String,
from_type: String,
#[label("can't convert {from_type} to {to_type}")]
span: Span,
#[label("conversion originates here")]
unit_span: Span,
#[help] #[help]
help: Option<String>, help: Option<String>,
}, },
@ -1238,6 +1244,22 @@ This is an internal Nushell error, please file an issue https://github.com/nushe
span: Span, span: Span,
}, },
/// Invalid unit
///
/// ## Resolution
///
/// Correct unit
#[error("Invalid unit")]
#[diagnostic(
code(nu::shell::invalid_unit),
help("Supported units are: {supported_units}")
)]
InvalidUnit {
supported_units: String,
#[label("encountered here")]
span: Span,
},
/// Tried spreading a non-list inside a list or command call. /// Tried spreading a non-list inside a list or command call.
/// ///
/// ## Resolution /// ## Resolution

View File

@ -27,7 +27,7 @@ mod ty;
mod value; mod value;
pub use alias::*; pub use alias::*;
pub use ast::Unit; pub use ast::unit::*;
pub use config::*; pub use config::*;
pub use did_you_mean::did_you_mean; pub use did_you_mean::did_you_mean;
pub use engine::{ENV_VARIABLE_ID, IN_VARIABLE_ID, NU_VARIABLE_ID}; pub use engine::{ENV_VARIABLE_ID, IN_VARIABLE_ID, NU_VARIABLE_ID};

View File

@ -10,6 +10,10 @@ use std::{
}; };
use thiserror::Error; use thiserror::Error;
pub const SUPPORTED_FILESIZE_UNITS: [&str; 13] = [
"B", "kB", "MB", "GB", "TB", "PB", "EB", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
];
/// A signed number of bytes. /// A signed number of bytes.
/// ///
/// [`Filesize`] is a wrapper around [`i64`]. Whereas [`i64`] is a dimensionless value, [`Filesize`] represents a /// [`Filesize`] is a wrapper around [`i64`]. Whereas [`i64`] is a dimensionless value, [`Filesize`] represents a