tmaze/settings/
attribute.rs

1use std::{fmt, str::FromStr};
2
3use crossterm::style::Attributes;
4use serde::Deserialize;
5
6macro_rules! Attribute {
7    (
8        $(
9            $(#[$inner:ident $($args:tt)*])*
10            $name:ident = $sgr:expr,
11        )*
12    ) => {
13        #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Deserialize)]
14        #[non_exhaustive]
15        #[serde(rename_all = "snake_case")]
16        pub enum Attribute {
17            $(
18                $(#[$inner $($args)*])*
19                $name,
20            )*
21        }
22
23        impl Attribute {
24            pub fn iterator() -> impl Iterator<Item = Attribute> {
25                use self::Attribute::*;
26                [ $($name,)* ].into_iter()
27            }
28        }
29    }
30}
31
32Attribute! {
33    /// Resets all the attributes.
34    Reset = 0,
35    /// Increases the text intensity.
36    Bold = 1,
37    /// Decreases the text intensity.
38    Dim = 2,
39    /// Emphasises the text.
40    Italic = 3,
41    /// Underlines the text.
42    Underlined = 4,
43
44    // Other types of underlining
45    /// Double underlines the text.
46    DoubleUnderlined = 2,
47    /// Undercurls the text.
48    Undercurled = 3,
49    /// Underdots the text.
50    Underdotted = 4,
51    /// Underdashes the text.
52    Underdashed = 5,
53
54    /// Makes the text blinking (< 150 per minute).
55    SlowBlink = 5,
56    /// Makes the text blinking (>= 150 per minute).
57    RapidBlink = 6,
58    /// Swaps foreground and background colors.
59    Reverse = 7,
60    /// Hides the text (also known as Conceal).
61    Hidden = 8,
62    /// Crosses the text.
63    CrossedOut = 9,
64    /// Sets the [Fraktur](https://en.wikipedia.org/wiki/Fraktur) typeface.
65    ///
66    /// Mostly used for [mathematical alphanumeric symbols](https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols).
67    Fraktur = 20,
68    /// Turns off the `Bold` attribute. - Inconsistent - Prefer to use NormalIntensity
69    NoBold = 21,
70    /// Switches the text back to normal intensity (no bold, italic).
71    NormalIntensity = 22,
72    /// Turns off the `Italic` attribute.
73    NoItalic = 23,
74    /// Turns off the `Underlined` attribute.
75    NoUnderline = 24,
76    /// Turns off the text blinking (`SlowBlink` or `RapidBlink`).
77    NoBlink = 25,
78    /// Turns off the `Reverse` attribute.
79    NoReverse = 27,
80    /// Turns off the `Hidden` attribute.
81    NoHidden = 28,
82    /// Turns off the `CrossedOut` attribute.
83    NotCrossedOut = 29,
84    /// Makes the text framed.
85    Framed = 51,
86    /// Makes the text encircled.
87    Encircled = 52,
88    /// Draws a line at the top of the text.
89    OverLined = 53,
90    /// Turns off the `Frame` and `Encircled` attributes.
91    NotFramedOrEncircled = 54,
92    /// Turns off the `OverLined` attribute.
93    NotOverLined = 55,
94}
95
96impl From<Attribute> for crossterm::style::Attribute {
97    fn from(value: Attribute) -> Self {
98        use crossterm::style;
99        use Attribute::*;
100
101        match value {
102            Reset => style::Attribute::Reset,
103            Bold => style::Attribute::Bold,
104            Dim => style::Attribute::Dim,
105            Italic => style::Attribute::Italic,
106            Underlined => style::Attribute::Underlined,
107            DoubleUnderlined => style::Attribute::DoubleUnderlined,
108            Undercurled => style::Attribute::Undercurled,
109            Underdotted => style::Attribute::Underdotted,
110            Underdashed => style::Attribute::Underdashed,
111            SlowBlink => style::Attribute::SlowBlink,
112            RapidBlink => style::Attribute::RapidBlink,
113            Reverse => style::Attribute::Reverse,
114            Hidden => style::Attribute::Hidden,
115            CrossedOut => style::Attribute::CrossedOut,
116            Fraktur => style::Attribute::Fraktur,
117            NoBold => style::Attribute::NoBold,
118            NormalIntensity => style::Attribute::NormalIntensity,
119            NoItalic => style::Attribute::NoItalic,
120            NoUnderline => style::Attribute::NoUnderline,
121            NoBlink => style::Attribute::NoBlink,
122            NoReverse => style::Attribute::NoReverse,
123            NoHidden => style::Attribute::NoHidden,
124            NotCrossedOut => style::Attribute::NotCrossedOut,
125            Framed => style::Attribute::Framed,
126            Encircled => style::Attribute::Encircled,
127            OverLined => style::Attribute::OverLined,
128            NotFramedOrEncircled => style::Attribute::NotFramedOrEncircled,
129            NotOverLined => style::Attribute::NotOverLined,
130        }
131    }
132}
133
134impl FromStr for Attribute {
135    type Err = ();
136
137    fn from_str(s: &str) -> Result<Self, Self::Err> {
138        match s {
139            "reset" => Ok(Attribute::Reset),
140            "bold" => Ok(Attribute::Bold),
141            "dim" => Ok(Attribute::Dim),
142            "italic" => Ok(Attribute::Italic),
143            "underlined" => Ok(Attribute::Underlined),
144            "double_underlined" => Ok(Attribute::DoubleUnderlined),
145            "undercurled" => Ok(Attribute::Undercurled),
146            "underdotted" => Ok(Attribute::Underdotted),
147            "underdashed" => Ok(Attribute::Underdashed),
148            "slow_blink" => Ok(Attribute::SlowBlink),
149            "rapid_blink" => Ok(Attribute::RapidBlink),
150            "reverse" => Ok(Attribute::Reverse),
151            "hidden" => Ok(Attribute::Hidden),
152            "crossed_out" => Ok(Attribute::CrossedOut),
153            "fraktur" => Ok(Attribute::Fraktur),
154            "no_bold" => Ok(Attribute::NoBold),
155            "normal_intensity" => Ok(Attribute::NormalIntensity),
156            "no_italic" => Ok(Attribute::NoItalic),
157            "no_underline" => Ok(Attribute::NoUnderline),
158            "no_blink" => Ok(Attribute::NoBlink),
159            "no_reverse" => Ok(Attribute::NoReverse),
160            "no_hidden" => Ok(Attribute::NoHidden),
161            "not_crossed_out" => Ok(Attribute::NotCrossedOut),
162            "framed" => Ok(Attribute::Framed),
163            "encircled" => Ok(Attribute::Encircled),
164            "overlined" => Ok(Attribute::OverLined),
165            "not_framed_or_encircled" => Ok(Attribute::NotFramedOrEncircled),
166            "not_overlined" => Ok(Attribute::NotOverLined),
167            _ => Err(()),
168        }
169    }
170}
171
172impl fmt::Display for Attribute {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        match self {
175            Attribute::Reset => write!(f, "reset"),
176            Attribute::Bold => write!(f, "bold"),
177            Attribute::Dim => write!(f, "dim"),
178            Attribute::Italic => write!(f, "italic"),
179            Attribute::Underlined => write!(f, "underlined"),
180            Attribute::DoubleUnderlined => write!(f, "double_underlined"),
181            Attribute::Undercurled => write!(f, "undercurled"),
182            Attribute::Underdotted => write!(f, "underdotted"),
183            Attribute::Underdashed => write!(f, "underdashed"),
184            Attribute::SlowBlink => write!(f, "slow_blink"),
185            Attribute::RapidBlink => write!(f, "rapid_blink"),
186            Attribute::Reverse => write!(f, "reverse"),
187            Attribute::Hidden => write!(f, "hidden"),
188            Attribute::CrossedOut => write!(f, "crossed_out"),
189            Attribute::Fraktur => write!(f, "fraktur"),
190            Attribute::NoBold => write!(f, "no_bold"),
191            Attribute::NormalIntensity => write!(f, "normal_intensity"),
192            Attribute::NoItalic => write!(f, "no_italic"),
193            Attribute::NoUnderline => write!(f, "no_underline"),
194            Attribute::NoBlink => write!(f, "no_blink"),
195            Attribute::NoReverse => write!(f, "no_reverse"),
196            Attribute::NoHidden => write!(f, "no_hidden"),
197            Attribute::NotCrossedOut => write!(f, "not_crossed_out"),
198            Attribute::Framed => write!(f, "framed"),
199            Attribute::Encircled => write!(f, "encircled"),
200            Attribute::OverLined => write!(f, "overlined"),
201            Attribute::NotFramedOrEncircled => write!(f, "not_framed_or_encircled"),
202            Attribute::NotOverLined => write!(f, "not_overlined"),
203        }
204    }
205}
206
207pub fn deserialize_attributes<'de, D>(deserializer: D) -> Result<Attributes, D::Error>
208where
209    D: serde::Deserializer<'de>,
210{
211    Vec::<String>::deserialize(deserializer).map(|vec| {
212        let mut attributes = Attributes::default();
213        for attr in vec {
214            attributes.set(
215                match Attribute::from_str(&attr) {
216                    Ok(t) => t,
217                    Err(_) => panic!(
218                        "could not decode attribute: {}, valid attributes: {:?}",
219                        attr,
220                        Attribute::iterator()
221                            .map(|a| a.to_string())
222                            .collect::<Vec<_>>() // TODO: print similar attributes
223                    ),
224                }
225                .into(),
226            );
227        }
228        attributes
229    })
230}