diff --git a/src/_assoc-path.scss b/src/_assoc-path.scss new file mode 100644 index 0000000..2c6e7e7 --- /dev/null +++ b/src/_assoc-path.scss @@ -0,0 +1,50 @@ +@use './internal/is'; +@import 'assoc'; +@import 'has'; +@import 'slice'; + +/// Makes a shallow clone of an object, setting or overriding the nodes required +/// to create the given path +/// +/// @group object +/// @param {Array} path the path to set +/// @param {*} val The new value +/// @param {Object} obj The object to clone +/// @return {Object} A new object equivalent to the original except along the specified path. +/// +/// @example scss - assoc-path +/// +/// $set-prop: assoc-path('a' 'b' 'c', 42, (a: (b: (c: 0)))); +/// @debug $set-prop; //=> (a: (b: (c: 42))) + +@function assoc-path($path, $val, $obj) { + @if length($path) == 0 { + @return $val; + } + + $idx: nth($path, 1); + + @if length($path) > 1 { + $next-obj: if( + is.map($obj) and has($idx, $obj), + map-get($obj, $idx), + () + ); + $val: assoc-path(slice(2, length($path), $path), $val, $next-obj); + } + + $result: (); + + @if is.number($idx) and is.list($obj) { + @if length($obj) == 0 { + $result: append($obj, $val); + } @else { + $result: set-nth($obj, $idx, $val); + } + } @else { + $_obj: if(is.null($obj), (), $obj); + $result: assoc($idx, $val, $_obj); + } + + @return $result; +} diff --git a/src/_pluck.scss b/src/_pluck.scss new file mode 100644 index 0000000..684e708 --- /dev/null +++ b/src/_pluck.scss @@ -0,0 +1,21 @@ +@import 'prop'; +@import 'map'; +@import 'last'; + +/// Returns a new list by plucking the same named property from all objects in +/// the supplied list. +/// +/// @group list +/// @param {Number|String} key The key name to pluck from each object. +/// @param {Array} arr The array to consider. +/// @return {Array} The list of values for the given key. +/// @see props +/// +/// @example scss - pluck +/// +/// $avenger-names: pluck('name', $avengers) +/// @debug $avenger-names; //=> ('Black Widow' 'Captain America' 'Hawkeye' 'Hulk' 'Iron man' 'Thor') + +@function pluck($key, $arr) { + @return map((prop, $key), $arr); +} diff --git a/src/_sass-fire.scss b/src/_sass-fire.scss index e5749d3..9529b40 100644 --- a/src/_sass-fire.scss +++ b/src/_sass-fire.scss @@ -4,6 +4,7 @@ @import 'all-pass'; @import 'any-pass'; @import 'aperture'; +@import 'assoc-path'; @import 'assoc'; @import 'compose'; @import 'concat'; @@ -53,6 +54,7 @@ @import 'paths'; @import 'pick'; @import 'pipe'; +@import 'pluck'; @import 'prepend'; @import 'product'; @import 'prop-eq'; diff --git a/test/_assoc-path.spec.scss b/test/_assoc-path.spec.scss new file mode 100644 index 0000000..e0ce13f --- /dev/null +++ b/test/_assoc-path.spec.scss @@ -0,0 +1,109 @@ +@import 'true'; +@import '../src/assoc-path'; +@import '../src/path'; + +@include describe('assoc-path [function]') { + @include it('overrides only what is necessary for the path') { + $obj1: ( + a: ( + b: 1, + c: 2, + d: ( + e: 3, + ), + ), + f: ( + g: ( + h: 4, + i: ( + 5, + 6, + 7, + ), + j: ( + k: 6, + l: 7, + ), + ), + ), + m: 8, + ); + $obj2: assoc-path(('f', 'g', 'i', 2), 42, $obj1); + @include assert-equal(path(f g i, $obj2), (5, 42, 7)); + @include assert-equal(path(a, $obj2), path(a, $obj1)); + @include assert-equal(path(m, $obj2), path(m, $obj1)); + @include assert-equal(path(f g h, $obj2), path(f g h, $obj1)); + @include assert-equal(path(f g j, $obj2), path(f g j, $obj1)); + } + + @include it( + 'is the equivalent of clone and setPath if the property is not on the original' + ) { + $obj1: ( + a: 1, + b: ( + c: 2, + d: 3, + ), + e: 4, + f: 5, + ); + $obj2: assoc-path(('x', 1, 'y'), 42, $obj1); + @include assert-equal( + $obj2, + ( + a: 1, + b: ( + c: 2, + d: 3, + ), + e: 4, + f: 5, + x: + append( + (), + ( + y: 42, + ) + ), + ) + ); + @include assert-equal(path(a, $obj2), path(a, $obj1)); + @include assert-equal(path(b, $obj2), path(b, $obj1)); + @include assert-equal(path(e, $obj2), path(e, $obj1)); + @include assert-equal(path(f, $obj2), path(f, $obj1)); + } + + @include it('empty path replaces the whole object') { + @include assert-equal( + assoc-path( + (), + 3, + ( + a: 1, + b: 2, + ) + ), + 3 + ); + } + + @include it('replaces `null` with a new object') { + @include assert-equal( + assoc-path( + ('foo', 'bar', 'baz'), + 42, + ( + foo: null, + ) + ), + ( + foo: ( + bar: ( + baz: 42, + ), + ), + ) + ); + } +} diff --git a/test/_pluck.spec.scss b/test/_pluck.spec.scss new file mode 100644 index 0000000..243545d --- /dev/null +++ b/test/_pluck.spec.scss @@ -0,0 +1,38 @@ +@import 'true'; +@import '../src/pluck'; + +@include describe('pluck [function]') { + $the-avengers: ( + ( + name: 'Black Widow', + age: 34, + ), + ( + name: 'Captain America', + age: 100, + ), + ( + name: 'Hawkeye', + age: 47, + ), + ( + name: 'Hulk', + age: 49, + ), + ( + name: 'Iron man', + age: '48', + ), + ( + name: 'Thor', + age: 'unknown', + ) + ); + + @include it('should appropriate property over an array') { + @include assert-equal( + pluck('name', $the-avengers), + ('Black Widow' 'Captain America' 'Hawkeye' 'Hulk' 'Iron man' 'Thor') + ); + } +}