From 2ff3524ac2a80c3fcd92d867b88bdbdd894ba962 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sat, 3 Jul 2021 23:17:31 -0400 Subject: [PATCH] support arg to map-merge --- src/builtin/functions/map.rs | 35 ++++++++++++++++++++++++++++++++--- src/lib.rs | 2 +- src/value/map.rs | 12 +++++++++++- tests/map.rs | 25 +++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/builtin/functions/map.rs b/src/builtin/functions/map.rs index 5572b12..f84ff57 100644 --- a/src/builtin/functions/map.rs +++ b/src/builtin/functions/map.rs @@ -1,3 +1,5 @@ +use std::mem; + use super::{Builtin, GlobalFunctionMap}; use crate::{ @@ -87,7 +89,12 @@ pub(crate) fn map_values(mut args: CallArgs, parser: &mut Parser<'_>) -> SassRes } pub(crate) fn map_merge(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { - args.max_args(2)?; + if args.len() == 1 { + return Err(("Expected $args to contain a key.", args.span()).into()); + } + + let last_position = args.len().saturating_sub(1); + let mut map1 = match args.get_err(0, "map1")? { Value::Map(m) => m, Value::List(v, ..) if v.is_empty() => SassMap::new(), @@ -100,7 +107,8 @@ pub(crate) fn map_merge(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu .into()) } }; - let map2 = match args.get_err(1, "map2")? { + + let mut map2 = match args.get_err(last_position, "map2")? { Value::Map(m) => m, Value::List(v, ..) if v.is_empty() => SassMap::new(), Value::ArgList(v) if v.is_empty() => SassMap::new(), @@ -112,7 +120,28 @@ pub(crate) fn map_merge(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu .into()) } }; - map1.merge(map2); + + let mut keys = args.get_variadic()?; + + if keys.is_empty() { + map1.merge(map2); + } else { + while let Some(key) = keys.pop() { + let mut new_map = SassMap::new(); + new_map.insert(key.node, Value::Map(mem::take(&mut map2))); + map2 = new_map; + } + + for (key, value) in map2 { + // if they are two maps sharing a key, merge the keys + if let (Some(Value::Map(map1)), Value::Map(map2)) = (map1.get_mut(&key), &value) { + map1.merge(map2.clone()); + } else { + map1.insert(key, value); + } + } + } + Ok(Value::Map(map1)) } diff --git a/src/lib.rs b/src/lib.rs index 5aff6b7..71a96f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,7 @@ grass input.scss // it is sometimes useful to break up `impl`s clippy::multiple_inherent_impl, // filter isn't fallible - clippy::filter_map, + clippy::manual_filter_map, clippy::else_if_without_else, clippy::new_ret_no_self, renamed_and_removed_lints, diff --git a/src/value/map.rs b/src/value/map.rs index fc2c3f9..6c76661 100644 --- a/src/value/map.rs +++ b/src/value/map.rs @@ -6,7 +6,7 @@ use crate::{ value::Value, }; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub(crate) struct SassMap(Vec<(Value, Value)>); impl PartialEq for SassMap { @@ -51,6 +51,16 @@ impl SassMap { Ok(None) } + pub fn get_mut(&mut self, key: &Value) -> Option<&mut Value> { + for (k, v) in &mut self.0 { + if k == key { + return Some(v); + } + } + + None + } + pub fn remove(&mut self, key: &Value) { self.0.retain(|(ref k, ..)| k.not_equals(key)); } diff --git a/tests/map.rs b/tests/map.rs index 2baa05c..144b3a8 100644 --- a/tests/map.rs +++ b/tests/map.rs @@ -95,6 +95,31 @@ test!( "a {\n color: inspect(map-merge((c: d, e: f), (c: 1, e: 2)));\n}\n", "a {\n color: (c: 1, e: 2);\n}\n" ); +test!( + map_merge_nested_empty, + "a {b: inspect(map-merge((c: ()), c, ()))}", + "a {\n b: (c: ());\n}\n" +); +test!( + map_merge_nested_overlapping_keys, + "a {b: inspect(map-merge((c: (d: e, f: g, h: i)), c, (j: 1, f: 2, k: 3)))}", + "a {\n b: (c: (d: e, f: 2, h: i, j: 1, k: 3));\n}\n" +); +test!( + map_merge_nested_intermediate_is_not_map, + "a {b: inspect(map-merge((c: 1), c, d, (e: f)))}", + "a {\n b: (c: (d: (e: f)));\n}\n" +); +test!( + map_merge_nested_leaf_is_not_map, + "a {b: inspect(map-merge((c: 1), c, (d: e)))}", + "a {\n b: (c: (d: e));\n}\n" +); +test!( + map_merge_nested_multiple_keys, + "a {b: inspect(map-merge((c: (d: (e: (f: (g: h))))), c, d, e, f, (g: 1)))}", + "a {\n b: (c: (d: (e: (f: (g: 1)))));\n}\n" +); error!( map_merge_map1_non_map, "a {\n color: map-merge(foo, (a: b));\n}\n", "Error: $map1: foo is not a map."