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