Add a map.deep-remove() function (#1091)

This commit is contained in:
Jennifer Thakar 2020-09-22 16:11:48 -07:00 committed by GitHub
parent 45703ab597
commit 56d979d04d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 15 deletions

View File

@ -48,6 +48,20 @@
[map-deep-merge]: https://sass-lang.com/documentation/modules/map#deep-merge
* Add a `map.deep-remove()` function. This allows you to remove keys from
nested maps by passing multiple keys. For example:
```
map.deep-remove(
(color: (primary: red, secondary: blue)),
color, primary
) // => (color: (secondary: blue))
```
See [the Sass documentation][map-deep-remove] for more details.
[map-deep-remove]: https://sass-lang.com/documentation/modules/map#deep-remove
### Dart API
* Add a `Value.tryMap()` function which returns the `Value` as a `SassMap` if

View File

@ -23,7 +23,15 @@ final global = UnmodifiableListView([
/// The Sass map module.
final module = BuiltInModule("map", functions: [
_get, _set, _merge, _remove, _keys, _values, _hasKey, _deepMerge //
_get,
_set,
_merge,
_remove,
_keys,
_values,
_hasKey,
_deepMerge,
_deepRemove
]);
final _get = _function("get", r"$map, $key, $keys...", (arguments) {
@ -72,7 +80,7 @@ final _merge = BuiltInCallable.overloadedFunction("merge", {
throw SassScriptException("Expected \$args to contain a map.");
}
var map2 = args.last.assertMap("map2");
return _modify(map1, args.sublist(0, args.length - 1), (oldValue) {
return _modify(map1, args.take(args.length - 1), (oldValue) {
var nestedMap = oldValue?.tryMap();
if (nestedMap == null) return map2;
return SassMap({...nestedMap.contents, ...map2.contents});
@ -86,6 +94,19 @@ final _deepMerge = _function("deep-merge", r"$map1, $map2", (arguments) {
return _deepMergeImpl(map1, map2);
});
final _deepRemove =
_function("deep-remove", r"$map, $key, $keys...", (arguments) {
var map = arguments[0].assertMap("map");
var keys = [arguments[1], ...arguments[2].asList];
return _modify(map, keys.take(keys.length - 1), (value) {
var nestedMap = value?.tryMap();
if (nestedMap?.contents?.containsKey(keys.last) ?? false) {
return SassMap(Map.of(nestedMap.contents)..remove(keys.last));
}
return value;
});
});
final _remove = BuiltInCallable.overloadedFunction("remove", {
// Because the signature below has an explicit `$key` argument, it doesn't
// allow zero keys to be passed. We want to allow that case, so we add an
@ -131,36 +152,42 @@ final _hasKey = _function("has-key", r"$map, $key, $keys...", (arguments) {
return SassBoolean(map.contents.containsKey(keys.last));
});
/// Updates the specified value in [map] by applying the [modify()] callback to
/// Updates the specified value in [map] by applying the [modify] callback to
/// it, then returns the resulting map.
///
/// If more than one key is provided, this means the map targeted for update is
/// nested within [map]. The multiple [keys] form a path of nested maps that
/// leads to the targeted map. If any value along the path is not a map, and
/// [overwrite] is true, this inserts a new map at that key and overwrites the
/// current value. Otherwise, this fails and returns [map] with no changes.
SassMap _modify(SassMap map, List<Value> keys, Value modify(Value old),
[bool overwrite = true]) {
SassMap _modifyNestedMap(SassMap map, int index) {
/// `modify(null)` returns null, this inserts a new map at that key and
/// overwrites the current value. Otherwise, this fails and returns [map] with
/// no changes.
///
/// If no keys are provided, this passes [map] directly to modify and returns
/// the result.
Value _modify(SassMap map, Iterable<Value> keys, Value modify(Value old)) {
var keyIterator = keys.iterator;
SassMap _modifyNestedMap(SassMap map, [Value newValue]) {
var mutableMap = Map.of(map.contents);
var key = keys[index];
var key = keyIterator.current;
if (index == keys.length - 1) {
mutableMap[key] = modify(mutableMap[key]);
if (!keyIterator.moveNext()) {
mutableMap[key] = newValue ?? modify(mutableMap[key]);
return SassMap(mutableMap);
}
var nestedMap = mutableMap[key]?.tryMap();
if (nestedMap == null && !overwrite) {
return SassMap(mutableMap);
if (nestedMap == null) {
// We pass null to `modify` here to indicate there's no existing value.
newValue = modify(null);
if (newValue == null) return SassMap(mutableMap);
}
nestedMap ??= const SassMap.empty();
mutableMap[key] = _modifyNestedMap(nestedMap, index + 1);
mutableMap[key] = _modifyNestedMap(nestedMap, newValue);
return SassMap(mutableMap);
}
return _modifyNestedMap(map, 0);
return keyIterator.moveNext() ? _modifyNestedMap(map) : modify(map);
}
/// Merges [map1] and [map2], with values in [map2] taking precedence.