rsass/sass/functions/
map.rs

1use super::{is_not, CallError, CheckedArg, FunctionMap, ResolvedArgs};
2use crate::css::{Value, ValueMap};
3use crate::value::ListSeparator;
4use crate::Scope;
5
6/// Create the `sass:map` standard module.
7///
8/// Should conform to
9/// [the specification](https://sass-lang.com/documentation/modules/map).
10pub fn create_module() -> Scope {
11    let mut f = Scope::builtin_module("sass:map");
12    let mut g = Scope::new_global(Default::default()); // anonymous
13
14    def!(f, deep_merge(map1, map2), |s| {
15        let mut map1 = s.get(name!(map1))?;
16        let map2 = s.get_map(name!(map2), as_va_map)?;
17        do_deep_merge(&mut map1, map2);
18        Ok(Value::Map(map1))
19    });
20
21    def_va!(f, deep_remove(map, key, keys), |s| {
22        let mut map = s.get(name!(map))?;
23        let key = s.get(name!(key))?;
24        let keychain = match s.get(name!(keys))? {
25            Value::ArgList(mut args) => {
26                args.positional.insert(0, key);
27                args.positional
28            }
29            Value::List(mut keys, ..) => {
30                keys.insert(0, key);
31                keys
32            }
33            Value::Null => vec![key],
34            single => vec![key, single],
35        };
36        do_deep_remove(&mut map, &keychain);
37        Ok(Value::Map(map))
38    });
39
40    // Common to get and has_key
41    fn find_value<'a>(
42        map: &'a ValueMap,
43        key: &Value,
44        keys: &Value,
45    ) -> Result<Option<&'a Value>, CallError> {
46        let mut val = map.get(key);
47        match keys {
48            Value::ArgList(args) => {
49                args.check_no_named().map_err(CallError::msg)?;
50                for k in &args.positional {
51                    match val {
52                        Some(Value::Map(m)) => {
53                            val = m.get(k);
54                        }
55                        _ => return Ok(None),
56                    }
57                }
58            }
59            Value::List(keys, ..) => {
60                for k in keys {
61                    match val {
62                        Some(Value::Map(m)) => {
63                            val = m.get(k);
64                        }
65                        _ => return Ok(None),
66                    }
67                }
68            }
69            Value::Null => (),
70            single_key => match val {
71                Some(Value::Map(m)) => {
72                    val = m.get(single_key);
73                }
74                _ => return Ok(None),
75            },
76        };
77        Ok(val)
78    }
79    def_va!(f, get(map, key, keys), |s| {
80        let map = s.get(name!(map))?;
81        Ok(find_value(&map, &s.get(name!(key))?, &s.get(name!(keys))?)?
82            .cloned()
83            .unwrap_or(Value::Null))
84    });
85    def_va!(f, has_key(map, key, keys), |s| {
86        let map = s.get(name!(map))?;
87        Ok(find_value(&map, &s.get(name!(key))?, &s.get(name!(keys))?)?
88            .is_some()
89            .into())
90    });
91    def!(f, keys(map), |s| {
92        let map: ValueMap = s.get(name!(map))?;
93        Ok(Value::List(
94            map.keys().cloned().collect(),
95            Some(ListSeparator::Comma),
96            false,
97        ))
98    });
99    def_va!(g, merge(map1, args), |s| {
100        let mut map1 = s.get(name!(map1))?;
101        let (keys, map2) = match s.get(name!(args))? {
102            Value::ArgList(mut args) => {
103                if let Some(map2) = args.only_named(&name!(map2)) {
104                    (vec![], as_va_map(map2).named(name!(map2))?)
105                } else {
106                    let mut values = args.positional;
107                    let map2 = values
108                        .pop()
109                        .ok_or_else(|| {
110                            CallError::msg("Expected $args to contain a key.")
111                        })?
112                        .try_into()
113                        .named(name!(map2))?;
114                    (values, map2)
115                }
116            }
117            direct => (vec![], direct.try_into().named(name!(map2))?),
118        };
119        fn do_merge(
120            mut keys: impl Iterator<Item = Value>,
121            map1: &mut ValueMap,
122            map2: ValueMap,
123        ) {
124            if let Some(key) = keys.next() {
125                if let Some(Value::Map(m1)) = map1.get_mut(&key) {
126                    do_merge(keys, m1, map2);
127                } else {
128                    let mut m2 = ValueMap::new();
129                    do_merge(keys, &mut m2, map2);
130                    map1.insert(key, Value::Map(m2));
131                }
132            } else {
133                for (key, value) in map2 {
134                    map1.insert(key, value);
135                }
136            }
137        }
138        do_merge(keys.into_iter(), &mut map1, map2);
139        Ok(Value::Map(map1))
140    });
141    def_va!(g, remove(map, keys), |s| {
142        let mut map: ValueMap = s.get(name!(map))?;
143        match s.get(name!(keys))? {
144            Value::ArgList(mut args) => {
145                if let Some(key) = args.named.remove(&name!(key)) {
146                    if args.positional.is_empty() {
147                        map.remove(&key);
148                    } else {
149                        return Err(CallError::msg(
150                            "Argument $key was passed both by position and by name."
151                        ));
152                    }
153                }
154                args.check_no_named().map_err(CallError::msg)?;
155                for key in args.positional {
156                    map.remove(&key);
157                }
158            }
159            Value::List(keys, ..) => {
160                for key in keys {
161                    map.remove(&key);
162                }
163            }
164            key => {
165                map.remove(&key);
166            }
167        }
168        Ok(Value::Map(map))
169    });
170    def_va!(g, set(map, args), set);
171    def!(f, values(map), |s| {
172        let map: ValueMap = s.get(name!(map))?;
173        Ok(Value::List(
174            map.values().cloned().collect(),
175            Some(ListSeparator::Comma),
176            false,
177        ))
178    });
179    f.expose_star(&g);
180    f
181}
182
183pub fn expose(m: &Scope, global: &mut FunctionMap) {
184    for (gname, lname) in &[
185        (name!(map_get), name!(get)),
186        (name!(map_set), name!(set)),
187        (name!(map_has_key), name!(has_key)),
188        (name!(map_keys), name!(keys)),
189        (name!(map_merge), name!(merge)),
190        (name!(map_remove), name!(remove)),
191        (name!(map_values), name!(values)),
192    ] {
193        global.insert(gname.clone(), m.get_lfunction(lname));
194    }
195}
196
197impl TryFrom<Value> for ValueMap {
198    type Error = String;
199    fn try_from(v: Value) -> Result<Self, String> {
200        match v {
201            Value::Map(m) => Ok(m),
202            // An empty map and an empty list looks the same
203            Value::List(ref l, ..) if l.is_empty() => Ok(Self::new()),
204            v => Err(is_not(&v, "a map")),
205        }
206    }
207}
208
209fn as_va_map(v: Value) -> Result<ValueMap, String> {
210    match v {
211        Value::ArgList(args) => {
212            args.check_no_named().map_err(|e| e.to_string())?;
213            let mut values = args.positional;
214            let mut result = if let Some(last) = values.pop() {
215                last.try_into()?
216            } else {
217                return Err("arglist unexpectedly empty".into());
218            };
219            while let Some(prev) = values.pop() {
220                result = ValueMap::singleton(prev, Value::Map(result));
221            }
222            Ok(result)
223        }
224        Value::List(mut values, ..) => {
225            let mut result = if let Some(last) = values.pop() {
226                last.try_into()?
227            } else {
228                ValueMap::new()
229            };
230            while let Some(prev) = values.pop() {
231                result = ValueMap::singleton(prev, Value::Map(result));
232            }
233            Ok(result)
234        }
235        v => v.try_into(),
236    }
237}
238
239fn do_deep_merge(map1: &mut ValueMap, map2: ValueMap) {
240    for (key, value) in map2 {
241        match (map1.get_mut(&key), value) {
242            (Some(Value::Map(ref mut m1)), Value::Map(m2)) => {
243                do_deep_merge(m1, m2);
244            }
245            (Some(Value::Map(_)), Value::List(ref l, ..)) if l.is_empty() => {
246                // nop; empty list is same thing as empty map
247            }
248            (_, v2) => {
249                map1.insert(key, v2);
250            }
251        }
252    }
253}
254
255fn do_deep_remove(map: &mut ValueMap, keys: &[Value]) {
256    match keys.len() {
257        0 => (), // Error?  Or just fine?
258        1 => {
259            map.remove(&keys[0]);
260        }
261        _ => {
262            if let Some(Value::Map(inner)) = map.get_mut(&keys[0]) {
263                do_deep_remove(inner, &keys[1..]);
264            }
265        }
266    }
267}
268
269fn set(s: &ResolvedArgs) -> Result<Value, CallError> {
270    let map = s.get(name!(map))?;
271    match s.get(name!(args))? {
272        Value::ArgList(mut args) => {
273            let keys = match args.named.remove(&"keys".into()) {
274                Some(Value::List(v, ..)) => Some(v),
275                Some(v) => Some(vec![v]),
276                None => None,
277            };
278            let key = args.named.remove(&"key".into());
279            if key.is_none() && keys.is_none() && args.positional.is_empty() {
280                return Err(CallError::msg(
281                    "Expected $args to contain a key.",
282                ));
283            }
284            let value = args
285                .named
286                .remove(&"value".into())
287                .or_else(|| {
288                    if key.is_some()
289                        || keys.is_some()
290                        || args.positional.len() > 1
291                    {
292                        args.positional.pop()
293                    } else {
294                        None
295                    }
296                })
297                .ok_or_else(|| {
298                    CallError::msg("Expected $args to contain a value.")
299                })?;
300
301            let mut keys = match (keys, args.positional.is_empty()) {
302                (Some(keys), true) => keys,
303                (None, _) => args.positional,
304                (Some(_), false) => {
305                    return Err(CallError::msg(
306                        "Got $keys both by name and by position.",
307                    ))
308                }
309            };
310            if let Some(key) = key {
311                keys.push(key);
312            }
313            Ok(Value::Map(set_inner(map, &keys, value)?))
314        }
315        Value::List(mut v, ..) => {
316            if let Some(value) = v.pop() {
317                Ok(Value::Map(set_inner(map, &v, value)?))
318            } else {
319                Err(CallError::msg("Expected $args to contain a key."))
320            }
321        }
322        Value::Map(mut args) => {
323            let mut keys = match args.remove(&"keys".into()) {
324                Some(Value::List(v, ..)) => v,
325                Some(v) => vec![v],
326                None => vec![],
327            };
328            if let Some(key) = args.remove(&"key".into()) {
329                keys.push(key);
330            }
331            let value = args.remove(&"value".into()).ok_or_else(|| {
332                CallError::msg("Expected $args to contain a value.")
333            })?;
334            Ok(Value::Map(set_inner(map, &keys, value)?))
335        }
336        _ => Err(CallError::msg("Expected $args to contain a value.")),
337    }
338}
339fn set_inner(
340    mut map: ValueMap,
341    keys: &[Value],
342    value: Value,
343) -> Result<ValueMap, CallError> {
344    if let Some((key, rest)) = keys.split_first() {
345        let value = if rest.is_empty() {
346            value
347        } else {
348            let inner = match map.remove(key) {
349                Some(Value::Map(inner)) => inner,
350                _ => ValueMap::new(),
351            };
352            Value::Map(set_inner(inner, rest, value)?)
353        };
354        map.insert(key.clone(), value);
355        Ok(map)
356    } else {
357        Err(CallError::msg("Expected $args to contain a value."))
358    }
359}
360
361#[cfg(test)]
362mod test {
363    // http://sass-lang.com/documentation/Sass/Script/Functions.html
364
365    mod map_get {
366        use super::check_val;
367
368        #[test]
369        fn a() {
370            check_val("map-get((\"foo\": 1, \"bar\": 2), \"foo\");", "1")
371        }
372        #[test]
373        fn b() {
374            check_val("map-get((\"foo\": 1, \"bar\": 2), \"bar\");", "2")
375        }
376        #[test]
377        fn c() {
378            check_val("map-get((\"foo\": 1, \"bar\": 2), \"baz\");", "")
379        }
380    }
381
382    mod map_has_key {
383        use super::check_val;
384
385        #[test]
386        fn a() {
387            check_val(
388                "map-has-key((\"foo\": 1, \"bar\": 2), \"foo\");",
389                "true",
390            )
391        }
392        #[test]
393        fn b() {
394            check_val(
395                "map-has-key((\"foo\": 1, \"bar\": 2), \"baz\");",
396                "false",
397            )
398        }
399    }
400
401    fn check_val(src: &str, correct: &str) {
402        use crate::variablescope::test::do_evaluate;
403        assert_eq!(do_evaluate(&[], src.as_bytes()), correct)
404    }
405}