etop_format/number_format/
str_convert.rs

1use super::types::{
2    FormatType, NumberAlign, NumberFormat, Sign, DEFAULT_PRECISION, DEFAULT_TIMEZONE,
3};
4use crate::FormatError;
5use regex::{Captures, Regex};
6use std::{fmt, str::FromStr};
7
8impl fmt::Display for FormatError {
9    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
10        // let message = match self {
11        //     FormatError::CouldNotParseFormatType => "could not parse format type",
12        //     FormatError::CouldNotDecomposeCoefficientExponent => {
13        //         "could not deomponse coefficient exponent"
14        //     }
15        //     FormatError::CouldNotCreateRegex => "could not create regex",
16        //     FormatError::CouldNotMatchRegex => "regex could not match",
17        //     FormatError::InvalidFormat(s) => s,
18        // };
19        let s = format!("{:?}", self);
20        write!(f, "{}", s)
21    }
22}
23
24impl FromStr for NumberAlign {
25    type Err = FormatError;
26
27    fn from_str(s: &str) -> Result<Self, Self::Err> {
28        match s {
29            ">" => Ok(NumberAlign::Right),
30            "<" => Ok(NumberAlign::Left),
31            "^" => Ok(NumberAlign::Center),
32            "=" => Ok(NumberAlign::SignedRight),
33            _ => Err(FormatError::CouldNotParseFormatType),
34        }
35    }
36}
37
38impl FromStr for FormatType {
39    type Err = FormatError;
40
41    fn from_str(s: &str) -> Result<Self, Self::Err> {
42        match s {
43            "e" => Ok(FormatType::Exponent),
44            "E" => Ok(FormatType::ExponentUppercase),
45            "f" => Ok(FormatType::FixedPoint),
46            "s" => Ok(FormatType::SI),
47            "%" => Ok(FormatType::Percentage),
48            "b" => Ok(FormatType::Binary),
49            "o" => Ok(FormatType::Octal),
50            "O" => Ok(FormatType::OctalUppercase),
51            "d" => Ok(FormatType::Decimal),
52            "x" => Ok(FormatType::Hex),
53            "X" => Ok(FormatType::HexUppercase),
54            _ => Err(FormatError::CouldNotParseFormatType),
55        }
56    }
57}
58
59impl TryFrom<&str> for NumberFormat {
60    type Error = FormatError;
61
62    fn try_from(pattern: &str) -> Result<NumberFormat, FormatError> {
63        let re =
64            Regex::new(r"^(?:(.)?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?([A-Za-z%])?$")
65                .map_err(|_| FormatError::CouldNotCreateRegex)?;
66        let captures = re.captures(pattern).ok_or(FormatError::CouldNotMatchRegex)?;
67        Ok(NumberFormat::from(captures))
68    }
69}
70
71impl From<Captures<'_>> for NumberFormat {
72    /// Create a `NumberFormat` instance from a parsed format pattern string.
73    fn from(c: Captures<'_>) -> Self {
74        let fill = c.get(1).and_then(|m| m.as_str().chars().next()).unwrap_or(' ');
75        let align = c
76            .get(2)
77            .map(|s| s.as_str().parse().unwrap_or(NumberAlign::Right))
78            .unwrap_or(NumberAlign::Right);
79        let sign = match c.get(3).map(|m| m.as_str()) {
80            Some("-") => Sign::OnlyNegative,
81            Some("+") => Sign::Always,
82            Some(" ") => Sign::SpaceOrDash,
83            _ => Sign::OnlyNegative,
84        };
85        let type_prefix = matches!(c.get(4).map(|m| m.as_str()), Some("#"));
86        let zero_padding = c.get(5).is_some();
87        let min_width = c.get(6).map(|m| m.as_str().parse().unwrap_or(0)).unwrap_or(0);
88        let commas = matches!(c.get(7).map(|m| m.as_str()), Some(","));
89        let precision = c
90            .get(8)
91            .map(|m| m.as_str().get(1..).unwrap_or_default().parse().unwrap_or(DEFAULT_PRECISION))
92            .unwrap_or(DEFAULT_PRECISION);
93        let format_type: FormatType =
94            c.get(9).and_then(|s| s.as_str().parse().ok()).unwrap_or(FormatType::None);
95
96        let timezone = DEFAULT_TIMEZONE;
97
98        let max_width = usize::MAX;
99        let mut spec = Self {
100            fill,
101            align,
102            sign,
103            type_prefix,
104            zero_padding,
105            min_width,
106            max_width,
107            commas,
108            precision,
109            format_type,
110            timezone,
111        };
112
113        // If zero fill is specified, padding goes after sign and before digits.
114        if spec.zero_padding || (spec.fill == '0' && spec.align == NumberAlign::SignedRight) {
115            spec.zero_padding = true;
116            spec.fill = '0';
117            spec.align = NumberAlign::SignedRight;
118        }
119
120        // Ignore precision for decimal notation.
121        if spec.format_type == FormatType::Decimal {
122            spec.precision = 0;
123        };
124
125        spec
126    }
127}