From eecff6d58a0a0d065a38ac7eafcce236b0a7d895 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 12 May 2023 02:34:25 +0000 Subject: [PATCH] implement builtin fn map.deep-remove --- .gitignore | 1 + CHANGELOG.md | 2 +- README.md | 8 +- crates/compiler/src/builtin/modules/map.rs | 93 +++++++++++++++++++++- crates/compiler/src/value/map.rs | 4 + crates/lib/tests/map-module.rs | 27 +++++++ 6 files changed, 127 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 143ae51..f36bcec 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ dart-sass sass-fairy duomo ibm-cloud-cognitive +pico diff --git a/CHANGELOG.md b/CHANGELOG.md index eaa08f5..05591c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ # TBD -- implement builtin map-module function `map.deep-merge(..)` +- implement builtin map-module functions `map.deep-merge(..)` and `map.deep-remove(..)` # 0.12.3 diff --git a/README.md b/README.md index a301402..f8a0d6f 100644 --- a/README.md +++ b/README.md @@ -84,10 +84,10 @@ The spec runner does not work on Windows. Using a modified version of the spec runner that ignores warnings and error spans (but does include error messages), `grass` achieves the following results: ``` -2022-01-31 -PASSING: 6248 -FAILING: 624 -TOTAL: 6892 +2022-05-11 +PASSING: 6277 +FAILING: 596 +TOTAL: 6905 ``` The majority of the failing tests are purely aesthetic, relating to whitespace diff --git a/crates/compiler/src/builtin/modules/map.rs b/crates/compiler/src/builtin/modules/map.rs index 95bcbfe..14c8ba7 100644 --- a/crates/compiler/src/builtin/modules/map.rs +++ b/crates/compiler/src/builtin/modules/map.rs @@ -1,3 +1,5 @@ +use std::iter::Peekable; + use crate::builtin::builtin_imports::*; use crate::builtin::{ @@ -16,9 +18,7 @@ fn deep_merge_impl(map1: SassMap, map2: SassMap) -> SassMap { let mut result = map1; for (key, value) in map2 { - let result_map = result.get_ref(&key.node).and_then(Value::try_map); - - match result_map { + match result.get_ref(&key.node).and_then(Value::try_map) { Some(result_map) => match value.try_map() { Some(value_map) => { let merged = deep_merge_impl(result_map, value_map); @@ -53,6 +53,92 @@ fn deep_merge(mut args: ArgumentResult, _: &mut Visitor) -> SassResult { Ok(Value::Map(deep_merge_impl(map1, map2))) } +fn deep_remove(mut args: ArgumentResult, _: &mut Visitor) -> SassResult { + let span = args.span(); + + let map = args.get_err(0, "map")?.assert_map_with_name("map", span)?; + + let key = args.get_err(1, "key")?; + let keys = args.get_variadic()?.into_iter().map(|arg| arg.node); + let mut keys = std::iter::once(key).chain(keys).collect::>(); + + let last = keys.pop(); + + let map = modify_map( + map, + keys.into_iter(), + |value| { + let last = match last.as_ref() { + Some(v) => v, + None => return value, + }; + + match value.try_map() { + Some(mut nested_map) if nested_map.contains(last) => { + nested_map.remove(last); + Value::Map(nested_map) + } + Some(..) | None => value, + } + }, + false, + span, + ); + + Ok(map) +} + +fn modify_map( + map: SassMap, + keys: impl Iterator, + modify: impl Fn(Value) -> Value, + // default=true + add_nesting: bool, + span: Span, +) -> Value { + let mut keys = keys.peekable(); + fn modify_nested_map( + mut mutable_map: SassMap, + mut keys: Peekable>, + add_nesting: bool, + span: Span, + modify: impl Fn(Value) -> Value, + ) -> SassMap { + let key = keys.next().unwrap(); + + if keys.peek().is_none() { + let value = modify(mutable_map.get_ref(&key).cloned().unwrap_or(Value::Null)); + mutable_map.insert(key.span(span), value); + return mutable_map; + } + + let nested_map = mutable_map.get_ref(&key).and_then(|v| v.try_map()); + + if nested_map.is_none() && !add_nesting { + return mutable_map; + } + + mutable_map.insert( + key.span(span), + Value::Map(modify_nested_map( + nested_map.unwrap_or_default(), + keys, + add_nesting, + span, + modify, + )), + ); + + mutable_map + } + + if keys.peek().is_some() { + Value::Map(modify_nested_map(map, keys, add_nesting, span, modify)) + } else { + modify(Value::Map(map)) + } +} + pub(crate) fn declare(f: &mut Module) { f.insert_builtin("get", map_get); f.insert_builtin("has-key", map_has_key); @@ -62,4 +148,5 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("values", map_values); f.insert_builtin("set", map_set); f.insert_builtin("deep-merge", deep_merge); + f.insert_builtin("deep-remove", deep_remove); } diff --git a/crates/compiler/src/value/map.rs b/crates/compiler/src/value/map.rs index 6b609be..9060feb 100644 --- a/crates/compiler/src/value/map.rs +++ b/crates/compiler/src/value/map.rs @@ -81,6 +81,10 @@ impl SassMap { self.0.into_iter().map(|(.., v)| v).collect() } + pub fn contains(&self, key: &Value) -> bool { + self.0.iter().any(|(k, ..)| &k.node == key) + } + pub fn as_list(self) -> Vec { self.0 .into_iter() diff --git a/crates/lib/tests/map-module.rs b/crates/lib/tests/map-module.rs index eb5f9cd..4ed8546 100644 --- a/crates/lib/tests/map-module.rs +++ b/crates/lib/tests/map-module.rs @@ -133,3 +133,30 @@ test!( }"#, "a {\n color: (c: (d: e));\n}\n" ); +test!( + deep_remove_key_dne, + r#"@use "sass:map"; + + a { + color: inspect(map.deep-remove((a: b), 1)); + }"#, + "a {\n color: (a: b);\n}\n" +); +test!( + deep_remove_nested_remove, + r#"@use "sass:map"; + + a { + color: inspect(map.deep-remove((c: (d: e)), c, d)); + }"#, + "a {\n color: (c: ());\n}\n" +); +test!( + deep_remove_nested_keys_dne, + r#"@use "sass:map"; + + a { + color: inspect(map.deep-remove((c: (d: e)), c, d, e, f, g)); + }"#, + "a {\n color: (c: (d: e));\n}\n" +);