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
38impl Unit {
39    pub fn build_value(self, size: i64, span: Span) -> Result<Value, ShellError> {
40        match self {
41            Unit::Filesize(unit) => {
42                if let Some(filesize) = Filesize::from_unit(size, unit) {
43                    Ok(filesize.into_value(span))
44                } else {
45                    Err(ShellError::GenericError {
46                        error: "filesize too large".into(),
47                        msg: "filesize too large".into(),
48                        span: Some(span),
49                        help: None,
50                        inner: vec![],
51                    })
52                }
53            }
54            Unit::Nanosecond => Ok(Value::duration(size, span)),
55            Unit::Microsecond => Ok(Value::duration(size * 1000, span)),
56            Unit::Millisecond => Ok(Value::duration(size * 1000 * 1000, span)),
57            Unit::Second => Ok(Value::duration(size * 1000 * 1000 * 1000, span)),
58            Unit::Minute => match size.checked_mul(1000 * 1000 * 1000 * 60) {
59                Some(val) => Ok(Value::duration(val, span)),
60                None => Err(ShellError::GenericError {
61                    error: "duration too large".into(),
62                    msg: "duration too large".into(),
63                    span: Some(span),
64                    help: None,
65                    inner: vec![],
66                }),
67            },
68            Unit::Hour => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60) {
69                Some(val) => Ok(Value::duration(val, span)),
70                None => Err(ShellError::GenericError {
71                    error: "duration too large".into(),
72                    msg: "duration too large".into(),
73                    span: Some(span),
74                    help: None,
75                    inner: vec![],
76                }),
77            },
78            Unit::Day => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24) {
79                Some(val) => Ok(Value::duration(val, span)),
80                None => Err(ShellError::GenericError {
81                    error: "duration too large".into(),
82                    msg: "duration too large".into(),
83                    span: Some(span),
84                    help: None,
85                    inner: vec![],
86                }),
87            },
88            Unit::Week => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 7) {
89                Some(val) => Ok(Value::duration(val, span)),
90                None => Err(ShellError::GenericError {
91                    error: "duration too large".into(),
92                    msg: "duration too large".into(),
93                    span: Some(span),
94                    help: None,
95                    inner: vec![],
96                }),
97            },
98        }
99    }
100
101    /// Returns the symbol [`str`] for a [`Unit`].
102    ///
103    /// The returned string is the same exact string needed for a successful call to
104    /// [`parse`](str::parse) for a [`Unit`].
105    ///
106    /// # Examples
107    /// ```
108    /// # use nu_protocol::{Unit, FilesizeUnit};
109    /// assert_eq!(Unit::Nanosecond.as_str(), "ns");
110    /// assert_eq!(Unit::Filesize(FilesizeUnit::B).as_str(), "B");
111    /// assert_eq!(Unit::Second.as_str().parse(), Ok(Unit::Second));
112    /// assert_eq!(Unit::Filesize(FilesizeUnit::KB).as_str().parse(), Ok(Unit::Filesize(FilesizeUnit::KB)));
113    /// ```
114    pub const fn as_str(&self) -> &'static str {
115        match self {
116            Unit::Filesize(u) => u.as_str(),
117            Unit::Nanosecond => "ns",
118            Unit::Microsecond => "us",
119            Unit::Millisecond => "ms",
120            Unit::Second => "sec",
121            Unit::Minute => "min",
122            Unit::Hour => "hr",
123            Unit::Day => "day",
124            Unit::Week => "wk",
125        }
126    }
127}
128
129impl FromStr for Unit {
130    type Err = ParseUnitError;
131
132    fn from_str(s: &str) -> Result<Self, Self::Err> {
133        if let Ok(filesize_unit) = FilesizeUnit::from_str(s) {
134            return Ok(Unit::Filesize(filesize_unit));
135        };
136
137        match s {
138            "ns" => Ok(Unit::Nanosecond),
139            "us" | "µs" => Ok(Unit::Microsecond),
140            "ms" => Ok(Unit::Millisecond),
141            "sec" => Ok(Unit::Second),
142            "min" => Ok(Unit::Minute),
143            "hr" => Ok(Unit::Hour),
144            "day" => Ok(Unit::Day),
145            "wk" => Ok(Unit::Week),
146            _ => Err(ParseUnitError(())),
147        }
148    }
149}