1 use super::{Builtin, GlobalFunctionMap};
2 
3 use crate::{
4     args::CallArgs, common::QuoteKind, error::SassResult, parse::Parser, unit::Unit, value::Number,
5     value::Value,
6 };
7 
8 /// Check if `s` matches the regex `^[a-zA-Z]+\s*=`
is_ms_filter(s: &str) -> bool9 fn is_ms_filter(s: &str) -> bool {
10     let mut bytes = s.bytes();
11 
12     if !bytes.next().map_or(false, |c| c.is_ascii_alphabetic()) {
13         return false;
14     }
15 
16     bytes
17         .skip_while(u8::is_ascii_alphabetic)
18         .find(|c| !matches!(c, b' ' | b'\t' | b'\n'))
19         == Some(b'=')
20 }
21 
22 #[cfg(test)]
23 mod test {
24     use super::is_ms_filter;
25     #[test]
test_is_ms_filter()26     fn test_is_ms_filter() {
27         assert!(is_ms_filter("a=a"));
28         assert!(is_ms_filter("a="));
29         assert!(is_ms_filter("a  \t\n  =a"));
30         assert!(!is_ms_filter("a  \t\n  a=a"));
31         assert!(!is_ms_filter("aa"));
32         assert!(!is_ms_filter("   aa"));
33         assert!(!is_ms_filter("=a"));
34         assert!(!is_ms_filter("1=a"));
35     }
36 }
37 
alpha(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>38 pub(crate) fn alpha(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
39     if args.len() <= 1 {
40         match args.get_err(0, "color")? {
41             Value::Color(c) => Ok(Value::Dimension(Some(c.alpha()), Unit::None, true)),
42             Value::String(s, QuoteKind::None) if is_ms_filter(&s) => {
43                 Ok(Value::String(format!("alpha({})", s), QuoteKind::None))
44             }
45             v => Err((
46                 format!("$color: {} is not a color.", v.inspect(args.span())?),
47                 args.span(),
48             )
49                 .into()),
50         }
51     } else {
52         let err = args.max_args(1);
53         let args = args
54             .get_variadic()?
55             .into_iter()
56             .map(|arg| match arg.node {
57                 Value::String(s, QuoteKind::None) if is_ms_filter(&s) => Ok(s),
58                 _ => {
59                     err.clone()?;
60                     unreachable!()
61                 }
62             })
63             .collect::<SassResult<Vec<String>>>()?;
64 
65         Ok(Value::String(
66             format!("alpha({})", args.join(", "),),
67             QuoteKind::None,
68         ))
69     }
70 }
71 
opacity(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>72 pub(crate) fn opacity(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
73     args.max_args(1)?;
74     match args.get_err(0, "color")? {
75         Value::Color(c) => Ok(Value::Dimension(Some(c.alpha()), Unit::None, true)),
76         Value::Dimension(Some(num), unit, _) => Ok(Value::String(
77             format!("opacity({}{})", num.inspect(), unit),
78             QuoteKind::None,
79         )),
80         Value::Dimension(None, ..) => todo!(),
81         v => Err((
82             format!("$color: {} is not a color.", v.inspect(args.span())?),
83             args.span(),
84         )
85             .into()),
86     }
87 }
88 
89 // todo: unify `opacify` and `fade_in`
opacify(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>90 fn opacify(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
91     args.max_args(2)?;
92     let color = match args.get_err(0, "color")? {
93         Value::Color(c) => c,
94         v => {
95             return Err((
96                 format!("$color: {} is not a color.", v.inspect(args.span())?),
97                 args.span(),
98             )
99                 .into())
100         }
101     };
102     let amount = match args.get_err(1, "amount")? {
103         Value::Dimension(Some(n), u, _) => bound!(args, "amount", n, u, 0, 1),
104         Value::Dimension(None, ..) => todo!(),
105         v => {
106             return Err((
107                 format!("$amount: {} is not a number.", v.inspect(args.span())?),
108                 args.span(),
109             )
110                 .into())
111         }
112     };
113     Ok(Value::Color(Box::new(color.fade_in(amount))))
114 }
115 
fade_in(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>116 fn fade_in(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
117     args.max_args(2)?;
118     let color = match args.get_err(0, "color")? {
119         Value::Color(c) => c,
120         v => {
121             return Err((
122                 format!("$color: {} is not a color.", v.inspect(args.span())?),
123                 args.span(),
124             )
125                 .into())
126         }
127     };
128     let amount = match args.get_err(1, "amount")? {
129         Value::Dimension(Some(n), u, _) => bound!(args, "amount", n, u, 0, 1),
130         Value::Dimension(None, ..) => todo!(),
131         v => {
132             return Err((
133                 format!("$amount: {} is not a number.", v.inspect(args.span())?),
134                 args.span(),
135             )
136                 .into())
137         }
138     };
139     Ok(Value::Color(Box::new(color.fade_in(amount))))
140 }
141 
142 // todo: unify with `fade_out`
transparentize(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>143 fn transparentize(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
144     args.max_args(2)?;
145     let color = match args.get_err(0, "color")? {
146         Value::Color(c) => c,
147         v => {
148             return Err((
149                 format!("$color: {} is not a color.", v.inspect(args.span())?),
150                 args.span(),
151             )
152                 .into())
153         }
154     };
155     let amount = match args.get_err(1, "amount")? {
156         Value::Dimension(Some(n), u, _) => bound!(args, "amount", n, u, 0, 1),
157         Value::Dimension(None, ..) => todo!(),
158         v => {
159             return Err((
160                 format!("$amount: {} is not a number.", v.inspect(args.span())?),
161                 args.span(),
162             )
163                 .into())
164         }
165     };
166     Ok(Value::Color(Box::new(color.fade_out(amount))))
167 }
168 
fade_out(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>169 fn fade_out(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
170     args.max_args(2)?;
171     let color = match args.get_err(0, "color")? {
172         Value::Color(c) => c,
173         v => {
174             return Err((
175                 format!("$color: {} is not a color.", v.inspect(args.span())?),
176                 args.span(),
177             )
178                 .into())
179         }
180     };
181     let amount = match args.get_err(1, "amount")? {
182         Value::Dimension(Some(n), u, _) => bound!(args, "amount", n, u, 0, 1),
183         Value::Dimension(None, ..) => todo!(),
184         v => {
185             return Err((
186                 format!("$amount: {} is not a number.", v.inspect(args.span())?),
187                 args.span(),
188             )
189                 .into())
190         }
191     };
192     Ok(Value::Color(Box::new(color.fade_out(amount))))
193 }
194 
declare(f: &mut GlobalFunctionMap)195 pub(crate) fn declare(f: &mut GlobalFunctionMap) {
196     f.insert("alpha", Builtin::new(alpha));
197     f.insert("opacity", Builtin::new(opacity));
198     f.insert("opacify", Builtin::new(opacify));
199     f.insert("fade-in", Builtin::new(fade_in));
200     f.insert("transparentize", Builtin::new(transparentize));
201     f.insert("fade-out", Builtin::new(fade_out));
202 }
203