Skip to main content

nu_protocol/ast/
unit.rs

1use crate::{
2    Filesize, FilesizeUnit, IntoValue, ShellError, Span, Value, shell_error::generic::GenericError,
3};
4use serde::{Deserialize, Serialize};
5use std::fmt;
6use std::str::FromStr;
7use thiserror::Error;
8
9pub const SUPPORTED_DURATION_UNITS: [&str; 9] =
10    ["ns", "us", "µs", "ms", "sec", "min", "hr", "day", "wk"];
11
12/// The error returned when failing to parse a [`Unit`].
13///
14/// This occurs when the string being parsed does not exactly match the name of one of the
15/// enum cases in [`Unit`].
16#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)]
17pub struct ParseUnitError(());
18
19impl fmt::Display for ParseUnitError {
20    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
21        write!(fmt, "invalid file size or duration unit")
22    }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26pub enum Unit {
27    Filesize(FilesizeUnit),
28
29    // Duration units
30    Nanosecond,
31    Microsecond,
32    Millisecond,
33    Second,
34    Minute,
35    Hour,
36    Day,
37    Week,
38}
39
40// TODO: something like `Filesize::from_unit` in the future?
41fn duration_mul_and_check(size: i64, factor: i64, span: Span) -> Result<Value, ShellError> {
42    match size.checked_mul(factor) {
43        Some(val) => Ok(Value::duration(val, span)),
44        None => Err(ShellError::Generic(GenericError::new(
45            "duration too large",
46            "duration too large",
47            span,
48        ))),
49    }
50}
51
52impl Unit {
53    pub fn build_value(self, size: i64, span: Span) -> Result<Value, ShellError> {
54        match self {
55            Unit::Filesize(unit) => {
56                if let Some(filesize) = Filesize::from_unit(size, unit) {
57                    Ok(filesize.into_value(span))
58                } else {
59                    Err(ShellError::Generic(GenericError::new(
60                        "filesize too large",
61                        "filesize too large",
62                        span,
63                    )))
64                }
65            }
66            Unit::Nanosecond => Ok(Value::duration(size, span)),
67            Unit::Microsecond => duration_mul_and_check(size, 1000, span),
68            Unit::Millisecond => duration_mul_and_check(size, 1000 * 1000, span),
69            Unit::Second => duration_mul_and_check(size, 1000 * 1000 * 1000, span),
70            Unit::Minute => duration_mul_and_check(size, 1000 * 1000 * 1000 * 60, span),
71            Unit::Hour => duration_mul_and_check(size, 1000 * 1000 * 1000 * 60 * 60, span),
72            Unit::Day => duration_mul_and_check(size, 1000 * 1000 * 1000 * 60 * 60 * 24, span),
73            Unit::Week => duration_mul_and_check(size, 1000 * 1000 * 1000 * 60 * 60 * 24 * 7, span),
74        }
75    }
76
77    /// Returns the symbol [`str`] for a [`Unit`].
78    ///
79    /// The returned string is the same exact string needed for a successful call to
80    /// [`parse`](str::parse) for a [`Unit`].
81    ///
82    /// # Examples
83    /// ```
84    /// # use nu_protocol::{Unit, FilesizeUnit};
85    /// assert_eq!(Unit::Nanosecond.as_str(), "ns");
86    /// assert_eq!(Unit::Filesize(FilesizeUnit::B).as_str(), "B");
87    /// assert_eq!(Unit::Second.as_str().parse(), Ok(Unit::Second));
88    /// assert_eq!(Unit::Filesize(FilesizeUnit::KB).as_str().parse(), Ok(Unit::Filesize(FilesizeUnit::KB)));
89    /// ```
90    pub const fn as_str(&self) -> &'static str {
91        match self {
92            Unit::Filesize(u) => u.as_str(),
93            Unit::Nanosecond => "ns",
94            Unit::Microsecond => "us",
95            Unit::Millisecond => "ms",
96            Unit::Second => "sec",
97            Unit::Minute => "min",
98            Unit::Hour => "hr",
99            Unit::Day => "day",
100            Unit::Week => "wk",
101        }
102    }
103}
104
105impl FromStr for Unit {
106    type Err = ParseUnitError;
107
108    fn from_str(s: &str) -> Result<Self, Self::Err> {
109        if let Ok(filesize_unit) = FilesizeUnit::from_str(s) {
110            return Ok(Unit::Filesize(filesize_unit));
111        };
112
113        match s {
114            "ns" => Ok(Unit::Nanosecond),
115            "us" | "µs" => Ok(Unit::Microsecond),
116            "ms" => Ok(Unit::Millisecond),
117            "sec" => Ok(Unit::Second),
118            "min" => Ok(Unit::Minute),
119            "hr" => Ok(Unit::Hour),
120            "day" => Ok(Unit::Day),
121            "wk" => Ok(Unit::Week),
122            _ => Err(ParseUnitError(())),
123        }
124    }
125}