ansic_macros/
lib.rs

1/*
2 * Hey! This code does need a cleanup and some comments, so if you come over this
3 * and is looking to contribute, this is a good place to start!
4*/
5
6use detect::{ArgumentType, error, parse_arg};
7use error::AnsicMacroError;
8use proc_macro::{Delimiter, Literal, TokenStream, TokenTree};
9use styles::AnsiStyle;
10
11mod detect;
12mod error;
13mod styles;
14
15const ANSI_PREFIX: &'static str = "\x1b[";
16
17struct AnsiArg<'a> {
18    pub name: &'a str,
19    pub rgb: Option<(u8, u8, u8)>,
20    pub bright: bool,
21    pub bg: bool,
22}
23
24impl<'a> AnsiArg<'a> {
25    fn generate_ansi(self) -> Option<String> {
26        if let Some(style) = AnsiStyle::to_style(self.name) {
27            return Some(style.code());
28        } else if let Some(color) = AnsiStyle::to_color(self.bg, self.bright, self.name, self.rgb) {
29            return Some(color.code());
30        }
31
32        None
33    }
34}
35
36fn generate_ansiarg(args: Vec<ArgumentType>, name: &str, rgb: Option<(u8, u8, u8)>) -> AnsiArg {
37    let mut profile = AnsiArg {
38        name,
39        bright: false,
40        bg: false,
41        rgb,
42    };
43
44    for arg in args {
45        match arg {
46            ArgumentType::Background => profile.bg = true,
47            ArgumentType::Bright => profile.bright = true,
48        }
49    }
50
51    profile
52}
53
54fn generate(args: Vec<String>) -> Result<String, syn::Error> {
55    let mut args_iter = args.iter().peekable();
56
57    let mut name: Option<&str> = None;
58    let mut rgb: Option<(u8, u8, u8)> = None;
59    let mut args: Vec<ArgumentType> = Vec::new();
60
61    while let Some(arg) = args_iter.next() {
62        if arg == "rgb" {
63            if let Some(_) = name {
64                return Err(AnsicMacroError::MultipleColorStyleArgs.into());
65            }
66
67            name = Some(arg);
68            let (mut r, mut g, mut b): (u8, u8, u8) = (0, 0, 0);
69
70            for i in 0..3 {
71                if let Some(res) = args_iter.next() {
72                    if let Ok(num) = res.parse::<u8>() {
73                        match i {
74                            0 => r = num,
75                            1 => g = num,
76                            2 => b = num,
77                            _ => return Err(AnsicMacroError::Unreachable.into()),
78                        }
79                    } else {
80                        return Err(AnsicMacroError::InvalidRgbArg(i, res).into());
81                    }
82                } else {
83                    return Err(AnsicMacroError::MissingRgbArg(i).into());
84                }
85            }
86
87            rgb = Some((r, g, b));
88        } else if let Some(color) = AnsiStyle::to_color(false, false, arg, None) {
89            if let Some(_) = name {
90                return Err(AnsicMacroError::MultipleColorStyleArgs.into());
91            }
92
93            name = Some(arg)
94        } else if let Some(style) = AnsiStyle::to_style(arg) {
95            if let Some(_) = name {
96                return Err(AnsicMacroError::MultipleColorStyleArgs.into());
97            }
98
99            name = Some(arg)
100        } else if let Some(targ) = parse_arg(arg) {
101            args.push(targ);
102        } else {
103            return Err(AnsicMacroError::InvalidStyleAndColor(arg).into());
104        }
105    }
106
107    if let Some(fname) = name {
108        let arg = generate_ansiarg(args, fname, rgb);
109
110        if let Some(data) = arg.generate_ansi() {
111            return Ok(data);
112        }
113
114        return Err(AnsicMacroError::Unreachable.into());
115    }
116
117    Err(AnsicMacroError::NoStyleOrColorTarget.into())
118}
119
120fn generate_ansi(args: Vec<Vec<String>>) -> Result<String, syn::Error> {
121    let mut ansi_code = ANSI_PREFIX.to_string();
122
123    let argslen = args.len();
124
125    for (i, arg) in args.into_iter().enumerate() {
126        let parsed = generate(arg)?;
127
128        if argslen - 1 == i {
129            ansi_code.push_str(&format!("{}m", parsed));
130        } else {
131            ansi_code.push_str(&format!("{};", parsed));
132        }
133    }
134
135    Ok(ansi_code)
136}
137
138fn generate_tokens(tokens: TokenStream) -> Result<Vec<Vec<String>>, syn::Error> {
139    let mut result = Vec::new();
140    let mut current = Vec::new();
141
142    let mut token_iter = tokens.into_iter().peekable();
143
144    while let Some(token) = token_iter.next() {
145        match &token {
146            TokenTree::Ident(ident) => {
147                current.push(ident.to_string());
148
149                if ident.to_string() == "rgb" {
150                    match token_iter.peek() {
151                        Some(TokenTree::Group(parens)) => {
152                            if parens.delimiter() == Delimiter::Parenthesis {
153                                // pstream is the tokenstream inside  theparenthesis
154                                let pstream = parens.stream();
155                                let mut stream_iter = pstream.into_iter().peekable();
156
157                                while let Some(token) = stream_iter.next() {
158                                    match token {
159                                        TokenTree::Literal(lit) => {
160                                            let s = lit.to_string();
161
162                                            if let Ok(num) = s.parse::<u8>() {
163                                                current.push(s);
164
165                                                match stream_iter.peek() {
166                                                    Some(TokenTree::Punct(p))
167                                                        if p.as_char() == ',' =>
168                                                    {
169                                                        stream_iter.next();
170                                                    }
171                                                    _ => {
172                                                        result.push(current);
173                                                        current = Vec::new();
174                                                    }
175                                                }
176                                            } else {
177                                                return Err(AnsicMacroError::RgbArgNotU8(&s).into());
178                                            }
179                                        }
180                                        _ => {}
181                                    }
182                                }
183                            }
184                        }
185                        _ => {
186                            return Err(AnsicMacroError::ExpectedRgbSyntax.into());
187                        }
188                    }
189
190                    continue;
191                }
192
193                match token_iter.peek() {
194                    Some(TokenTree::Punct(p)) if p.as_char() == '.' => {
195                        token_iter.next();
196                    }
197                    _ => {
198                        result.push(current);
199                        current = Vec::new();
200                    }
201                }
202            }
203            _ => {}
204        }
205    }
206
207    Ok(result)
208}
209
210#[proc_macro]
211pub fn ansi(input: TokenStream) -> TokenStream {
212    let tokens = generate_tokens(input);
213
214    if let Ok(args) = tokens {
215        let result = generate_ansi(args);
216
217        if let Ok(ansi_code) = result {
218            let lit = TokenTree::Literal(Literal::string(&ansi_code));
219            return TokenStream::from(lit);
220        } else if let Err(err) = result {
221            return err.to_compile_error().into();
222        } else {
223            let err: syn::Error = AnsicMacroError::Unreachable.into();
224
225            return err.to_compile_error().into();
226        }
227    } else if let Err(err) = tokens {
228        return err.to_compile_error().into();
229    } else {
230        let err: syn::Error = AnsicMacroError::Unreachable.into();
231
232        return err.to_compile_error().into();
233    }
234}