1 use proc_macro2::{Span, TokenStream};
2 use crate::{
3     err::Error,
4     ir::{Style, Color},
5 };
6 use super::{parse, expect_str_literal};
7 
8 
9 impl Style {
10     /// Parses the style specifiction assuming the token stream contains a
11     /// single string literal.
parse_from_tokens(tokens: TokenStream) -> Result<Self, Error>12     pub(crate) fn parse_from_tokens(tokens: TokenStream) -> Result<Self, Error> {
13         let (s, span) = parse(tokens, expect_str_literal)?;
14         Self::parse(&s, span)
15     }
16 
17     /// Parses the style specification in `spec` (with `span`) and returns a token
18     /// stream representing an expression constructing the corresponding `ColorSpec`
19     /// value.
parse(spec: &str, span: Span) -> Result<Self, Error>20     pub(super) fn parse(spec: &str, span: Span) -> Result<Self, Error> {
21         let mut out = Self::default();
22 
23         let mut previous_fg_color = None;
24         let mut previous_bg_color = None;
25         for fragment in spec.split('+').map(str::trim).filter(|s| !s.is_empty()) {
26             let (fragment, is_bg) = match fragment.strip_prefix("bg:") {
27                 Some(color) => (color, true),
28                 None => (fragment, false),
29             };
30 
31             // Parse/obtain color if a color is specified.
32             let color = match fragment {
33                 "black" => Some(Color::Black),
34                 "blue" => Some(Color::Blue),
35                 "green" => Some(Color::Green),
36                 "red" => Some(Color::Red),
37                 "cyan" => Some(Color::Cyan),
38                 "magenta" => Some(Color::Magenta),
39                 "yellow" => Some(Color::Yellow),
40                 "white" => Some(Color::White),
41 
42                 hex if hex.starts_with('#') => {
43                     let hex = &hex[1..];
44 
45                     if hex.len() != 6 {
46                         let e = err!(
47                             span,
48                             "hex color code invalid: 6 digits expected, found {}",
49                             hex.len(),
50                         );
51                         return Err(e);
52                     }
53 
54                     let digits = hex.chars()
55                         .map(|c| {
56                             c.to_digit(16).ok_or_else(|| {
57                                 err!(span, "hex color code invalid: {} is not a valid hex digit", c)
58                             })
59                         })
60                         .collect::<Result<Vec<_>, _>>()?;
61 
62                     let r = (digits[0] * 16 + digits[1]) as u8;
63                     let g = (digits[2] * 16 + digits[3]) as u8;
64                     let b = (digits[4] * 16 + digits[5]) as u8;
65 
66                     Some(Color::Rgb(r, g, b))
67                 },
68 
69                 // TODO: Ansi256 colors
70                 _ => None,
71             };
72 
73             // Check for duplicate color definitions.
74             let (previous_color, color_kind) = match is_bg {
75                 true => (&mut previous_bg_color, "background"),
76                 false => (&mut previous_fg_color, "foreground"),
77             };
78             match (&color, *previous_color) {
79                 (Some(_), Some(old)) => {
80                     let e = err!(
81                         span,
82                         "found '{}' but the {} color was already specified as '{}'",
83                         fragment,
84                         color_kind,
85                         old,
86                     );
87                     return Err(e);
88                 }
89                 (Some(_), None) => *previous_color = Some(fragment),
90                 _ => {}
91             }
92 
93             macro_rules! set_attr {
94                 ($field:ident, $value:expr) => {{
95                     if let Some(b) = out.$field {
96                         let field_s = stringify!($field);
97                         let old = if b { field_s.into() } else { format!("!{}", field_s) };
98                         let new = if $value { field_s.into() } else { format!("!{}", field_s) };
99                         let e = err!(
100                             span,
101                             "invalid style definition: found '{}', but '{}' was specified before",
102                             new,
103                             old,
104                         );
105                         return Err(e);
106                     }
107 
108                     out.$field = Some($value);
109                 }};
110             }
111 
112             // Obtain the final token stream for method call.
113             match (is_bg, color, fragment) {
114                 (false, Some(color), _) => out.fg = Some(color),
115                 (true, Some(color), _) => out.bg = Some(color),
116                 (true, None, other) => {
117                     return Err(err!(span, "'{}' (following 'bg:') is not a valid color", other));
118                 }
119 
120                 (false, None, "bold") => set_attr!(bold, true),
121                 (false, None, "!bold") => set_attr!(bold, false),
122                 (false, None, "italic") => set_attr!(italic, true),
123                 (false, None, "!italic") => set_attr!(italic, false),
124                 (false, None, "dimmed") => set_attr!(dimmed, true),
125                 (false, None, "!dimmed") => set_attr!(dimmed, false),
126                 (false, None, "underline") => set_attr!(underline, true),
127                 (false, None, "!underline") => set_attr!(underline, false),
128                 (false, None, "intense") => set_attr!(intense, true),
129                 (false, None, "!intense") => set_attr!(intense, false),
130 
131                 (false, None, other) => {
132                     return Err(err!(span, "invalid style spec fragment '{}'", other));
133                 }
134             }
135         }
136 
137         Ok(out)
138     }
139 }
140