implement builtin fn map.deep-remove

This commit is contained in:
Connor Skees 2023-05-12 02:34:25 +00:00
parent f811b243c7
commit eecff6d58a
6 changed files with 127 additions and 8 deletions

1
.gitignore vendored
View File

@ -36,3 +36,4 @@ dart-sass
sass-fairy sass-fairy
duomo duomo
ibm-cloud-cognitive ibm-cloud-cognitive
pico

View File

@ -9,7 +9,7 @@
# TBD # TBD
- implement builtin map-module function `map.deep-merge(..)` - implement builtin map-module functions `map.deep-merge(..)` and `map.deep-remove(..)`
# 0.12.3 # 0.12.3

View File

@ -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: 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 2022-05-11
PASSING: 6248 PASSING: 6277
FAILING: 624 FAILING: 596
TOTAL: 6892 TOTAL: 6905
``` ```
The majority of the failing tests are purely aesthetic, relating to whitespace The majority of the failing tests are purely aesthetic, relating to whitespace

View File

@ -1,3 +1,5 @@
use std::iter::Peekable;
use crate::builtin::builtin_imports::*; use crate::builtin::builtin_imports::*;
use crate::builtin::{ use crate::builtin::{
@ -16,9 +18,7 @@ fn deep_merge_impl(map1: SassMap, map2: SassMap) -> SassMap {
let mut result = map1; let mut result = map1;
for (key, value) in map2 { for (key, value) in map2 {
let result_map = result.get_ref(&key.node).and_then(Value::try_map); match result.get_ref(&key.node).and_then(Value::try_map) {
match result_map {
Some(result_map) => match value.try_map() { Some(result_map) => match value.try_map() {
Some(value_map) => { Some(value_map) => {
let merged = deep_merge_impl(result_map, value_map); let merged = deep_merge_impl(result_map, value_map);
@ -53,6 +53,92 @@ fn deep_merge(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
Ok(Value::Map(deep_merge_impl(map1, map2))) Ok(Value::Map(deep_merge_impl(map1, map2)))
} }
fn deep_remove(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
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::<Vec<_>>();
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<Item = Value>,
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<impl Iterator<Item = Value>>,
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) { pub(crate) fn declare(f: &mut Module) {
f.insert_builtin("get", map_get); f.insert_builtin("get", map_get);
f.insert_builtin("has-key", map_has_key); 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("values", map_values);
f.insert_builtin("set", map_set); f.insert_builtin("set", map_set);
f.insert_builtin("deep-merge", deep_merge); f.insert_builtin("deep-merge", deep_merge);
f.insert_builtin("deep-remove", deep_remove);
} }

View File

@ -81,6 +81,10 @@ impl SassMap {
self.0.into_iter().map(|(.., v)| v).collect() 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<Value> { pub fn as_list(self) -> Vec<Value> {
self.0 self.0
.into_iter() .into_iter()

View File

@ -133,3 +133,30 @@ test!(
}"#, }"#,
"a {\n color: (c: (d: e));\n}\n" "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"
);