nu_protocol/ast/
unit.rs

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