From 142bc9da1093317edf171da058d81f99306754eb Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 4 Jul 2021 18:59:35 -0400 Subject: [PATCH] support builtin map.set --- src/builtin/functions/map.rs | 110 ++++++++++++++++++++++++++++++----- src/builtin/modules/map.rs | 3 +- src/color/mod.rs | 9 ++- src/value/map.rs | 10 ---- tests/map.rs | 60 +++++++++++++++++++ 5 files changed, 161 insertions(+), 31 deletions(-) diff --git a/src/builtin/functions/map.rs b/src/builtin/functions/map.rs index db1d1b3..e547c38 100644 --- a/src/builtin/functions/map.rs +++ b/src/builtin/functions/map.rs @@ -1,5 +1,3 @@ -use std::mem; - use super::{Builtin, GlobalFunctionMap}; use crate::{ @@ -93,7 +91,7 @@ pub(crate) fn map_merge(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu return Err(("Expected $args to contain a key.", args.span()).into()); } - let last_position = args.len().saturating_sub(1); + let map2_position = args.len().saturating_sub(1); let mut map1 = match args.get_err(0, "map1")? { Value::Map(m) => m, @@ -108,7 +106,7 @@ pub(crate) fn map_merge(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu } }; - let mut map2 = match args.get_err(last_position, "map2")? { + let map2 = match args.get_err(map2_position, "map2")? { Value::Map(m) => m, Value::List(v, ..) if v.is_empty() => SassMap::new(), Value::ArgList(v) if v.is_empty() => SassMap::new(), @@ -121,23 +119,43 @@ pub(crate) fn map_merge(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu } }; - let mut keys = args.get_variadic()?; + let 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; + let mut current_map = map1.clone(); + let mut map_queue = Vec::new(); + + for key in keys { + match current_map.get(&key) { + Some(Value::Map(m1)) => { + current_map = m1.clone(); + map_queue.push((key, m1)); + } + Some(..) | None => { + current_map = SassMap::new(); + map_queue.push((key, SassMap::new())); + } + } } - 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); + match map_queue.last_mut() { + Some((_, m)) => { + m.merge(map2); + } + None => unreachable!(), + }; + + while let Some((key, queued_map)) = map_queue.pop() { + match map_queue.last_mut() { + Some((_, map)) => { + map.insert(key.node, Value::Map(queued_map)); + } + None => { + map1.insert(key.node, Value::Map(queued_map)); + break; + } } } } @@ -165,6 +183,68 @@ pub(crate) fn map_remove(mut args: CallArgs, parser: &mut Parser<'_>) -> SassRes Ok(Value::Map(map)) } +pub(crate) fn map_set(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + let key_position = args.len().saturating_sub(2); + let value_position = args.len().saturating_sub(1); + + let mut map = match args.get_err(0, "map")? { + Value::Map(m) => m, + Value::List(v, ..) if v.is_empty() => SassMap::new(), + Value::ArgList(v) if v.is_empty() => SassMap::new(), + v => { + return Err(( + format!("$map: {} is not a map.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + let key = args.get_err(key_position, "key")?; + let value = args.get_err(value_position, "value")?; + + let keys = args.get_variadic()?; + + if keys.is_empty() { + map.insert(key, value); + } else { + let mut current_map = map.clone(); + let mut map_queue = Vec::new(); + + for key in keys { + match current_map.get(&key) { + Some(Value::Map(m1)) => { + current_map = m1.clone(); + map_queue.push((key, m1)); + } + Some(..) | None => { + current_map = SassMap::new(); + map_queue.push((key, SassMap::new())); + } + } + } + + match map_queue.last_mut() { + Some((_, m)) => m.insert(key, value), + None => unreachable!(), + }; + + while let Some((key, queued_map)) = map_queue.pop() { + match map_queue.last_mut() { + Some((_, next_map)) => { + next_map.insert(key.node, Value::Map(queued_map)); + } + None => { + map.insert(key.node, Value::Map(queued_map)); + break; + } + } + } + } + + Ok(Value::Map(map)) +} + pub(crate) fn declare(f: &mut GlobalFunctionMap) { f.insert("map-get", Builtin::new(map_get)); f.insert("map-has-key", Builtin::new(map_has_key)); diff --git a/src/builtin/modules/map.rs b/src/builtin/modules/map.rs index 13446ad..7dccb7b 100644 --- a/src/builtin/modules/map.rs +++ b/src/builtin/modules/map.rs @@ -1,5 +1,5 @@ use crate::builtin::{ - map::{map_get, map_has_key, map_keys, map_merge, map_remove, map_values}, + map::{map_get, map_has_key, map_keys, map_merge, map_remove, map_set, map_values}, modules::Module, }; @@ -10,4 +10,5 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("merge", map_merge); f.insert_builtin("remove", map_remove); f.insert_builtin("values", map_values); + f.insert_builtin("set", map_set); } diff --git a/src/color/mod.rs b/src/color/mod.rs index d37701b..59fea6f 100644 --- a/src/color/mod.rs +++ b/src/color/mod.rs @@ -547,14 +547,13 @@ impl Color { } if hue < Number::small_ratio(1, 6) { - return m1.clone() + (m2 - m1) * hue * Number::from(6); + m1.clone() + (m2 - m1) * hue * Number::from(6) } else if hue < Number::small_ratio(1, 2) { - return m2; + m2 } else if hue < Number::small_ratio(2, 3) { - return m1.clone() - + (m2 - m1) * (Number::small_ratio(2, 3) - hue) * Number::from(6); + m1.clone() + (m2 - m1) * (Number::small_ratio(2, 3) - hue) * Number::from(6) } else { - return m1; + m1 } } diff --git a/src/value/map.rs b/src/value/map.rs index efca3d2..37b7abe 100644 --- a/src/value/map.rs +++ b/src/value/map.rs @@ -51,16 +51,6 @@ impl SassMap { 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 144b3a8..4087f73 100644 --- a/tests/map.rs +++ b/tests/map.rs @@ -120,6 +120,66 @@ test!( "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" ); +test!( + map_set_nested_empty, + "@use 'sass:map'; a {b: inspect(map.set((c: ()), c, d, e, f))}", + "a {\n b: (c: (d: (e: f)));\n}\n" +); +test!( + map_set_update_existing, + "@use 'sass:map'; a {b: inspect(map.set((c: (d: e)), c, d, f))}", + "a {\n b: (c: (d: f));\n}\n" +); +test!( + map_set_new_key, + "@use 'sass:map'; a {b: inspect(map.set((c: (d: e)), c, f, g))}", + "a {\n b: (c: (d: e, f: g));\n}\n" +); +test!( + map_set_value_is_not_map, + "@use 'sass:map'; a {b: inspect(map.set((c: 1), c, d, f))}", + "a {\n b: (c: (d: f));\n}\n" +); +test!( + map_merge_merge_into_map_with_many_keys, + r#" + @use "sass:map"; + + $fonts: ( + "Helvetica": ( + "weights": ( + "regular": 400, + "medium": 500, + "bold": 700 + ) + ) + ); + + a { + color: inspect(map.merge($fonts, "Helvetica", "weights", "regular", (a: 300))); + }"#, + "a {\n color: (\"Helvetica\": (\"weights\": (\"regular\": (a: 300), \"medium\": 500, \"bold\": 700)));\n}\n" +); +test!( + map_merge_nested, + r#" + @use "sass:map"; + + $fonts: ( + "Helvetica": ( + "weights": ( + "regular": 400, + "medium": 500, + "bold": 700 + ) + ) + ); + + a { + color: inspect(map.set($fonts, "Helvetica", "weights", "regular", 300)); + }"#, + "a {\n color: (\"Helvetica\": (\"weights\": (\"regular\": 300, \"medium\": 500, \"bold\": 700)));\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."