From 56d979d04d6dcdb0426994cfd2ffddaa045c3aa1 Mon Sep 17 00:00:00 2001 From: Jennifer Thakar Date: Tue, 22 Sep 2020 16:11:48 -0700 Subject: [PATCH] Add a map.deep-remove() function (#1091) --- CHANGELOG.md | 14 ++++++++++ lib/src/functions/map.dart | 57 ++++++++++++++++++++++++++++---------- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 434f2098..af35624e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/lib/src/functions/map.dart b/lib/src/functions/map.dart index a886d701..76a1f228 100644 --- a/lib/src/functions/map.dart +++ b/lib/src/functions/map.dart @@ -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 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 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.