diff --git a/CHANGELOG.md b/CHANGELOG.md index f565e34c..10e36e8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ -# 1.34.1 +# 1.35.0 +- Add tree `treeLookup` +- Add deep path support to `lensProp` +- Add `unsetOn` + +# 1.34.1 - Ignore browser testing errors. - Add karma JSON reporter. - Only watch files and record videos/screenshots for the local test. diff --git a/README.md b/README.md index f75ed3f9..49d853f1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -futil-js +futil-js --- @@ -111,7 +111,7 @@ These methods provide alternative orderings that are sometimes more convenient. The idea of `In` methods is to name them by convention, so when ever you need a method that actually takes the collection first (e.g. a `get` where the data is static but the field is dynamic), you can just add `In` to the end (such as `getIn` which takes the object first) ### `On`s (Immutable False) -`extendOn`, `defaultsOn`, `mergeOn`, `setOn` +`extendOn`, `defaultsOn`, `mergeOn`, `setOn`, `unsetOn` lodash/fp likes to keep things pure, but sometimes JS can get pretty dirty. These methods are alternatives for working with data that--for whatever the use case is--needs to be mutable Any methods that interact with mutable data will use the `On` convention (as it is some action occuring `On` some data) @@ -133,7 +133,7 @@ Any method with uncapped iteratee arguments will use the `Indexed` convention. ### dotJoinWith `filterFunction -> data:array -> result:string` Compacts an array by the provided function, then joins it with '.' - + ### repeated `data:array -> result:array` Returns an array of elements that are repeated in the array. @@ -391,7 +391,7 @@ This the first main way you'll generally interact with the lens API #### lensProp `lensProp :: string -> object -> { get: () -> T, set: T -> T }` -Creates an object lens for a given property on an object. `.get` returns the value at that path and `set` places a new value at that path +Creates an object lens for a given property on an object. `.get` returns the value at that path and `set` places a new value at that path. Supports deep paths like lodash get/set. #### lensOf @@ -531,6 +531,10 @@ Like `treeToArray`, but accepts a customizer to process the tree nodes before pu `traverse -> tree -> [treeNodes]` Returns an array of the tree nodes that can't be traversed into in `pre-order`. +### treeLookup +`(traverse, buildIteratee) -> ([path], tree) -> treeNode` +Looks up a node matching a path, which defaults to lodash `iteratee` but can be customized with buildIteratee. + ### tree -`traverse -> {walk, reduce, toArray, toArrayBy, leaves}` +`(traverse, buildIteratee) -> {walk, reduce, toArray, toArrayBy, leaves, lookup}` Takes a traversal function and returns an object with all of the tree methods pre-applied with the traversal. This is useful if you want to use a few of the tree methods with a custom traversal and can provides a slightly nicer api. diff --git a/package.json b/package.json index 97384eaf..38db394d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "futil-js", - "version": "1.34.1", + "version": "1.35.0", "description": "F(unctional) util(ities). Resistance is futile.", "main": "lib/futil-js.js", "scripts": { diff --git a/src/conversion.js b/src/conversion.js index f39a74e2..c8a9886f 100644 --- a/src/conversion.js +++ b/src/conversion.js @@ -18,6 +18,8 @@ export const extendOn = _.extend.convert(mutable) export const defaultsOn = _.defaults.convert(mutable) export const mergeOn = _.merge.convert(mutable) export const setOn = _.set.convert(mutable) +// Curry required until https://github.com/lodash/lodash/issues/3440 is resolved +export let unsetOn = _.curryN(2, _.unset.convert({immutable: false})) // This reduce based version is easier to maintain but requires calling `F.inversions.fn` instead of `F.fn` const inversionList = ['get', 'pick', 'includes'] diff --git a/src/lens.js b/src/lens.js index 14e0b7c6..db1ea2ff 100644 --- a/src/lens.js +++ b/src/lens.js @@ -1,4 +1,5 @@ import _ from 'lodash/fp' +import {setOn} from './conversion' // Stubs export let functionLens = val => (...x) => { @@ -22,9 +23,10 @@ export let objToFn = lens => (...values) => // Lens Construction export let lensProp = (field, source) => ({ - get: () => source[field], + get: () => _.get(field, source),//source[field], set: value => { - source[field] = value + setOn(field, value, source) + // source[field] = value }, }) diff --git a/src/tree.js b/src/tree.js index 9f9aac1c..42b11464 100644 --- a/src/tree.js +++ b/src/tree.js @@ -33,10 +33,15 @@ export let treeToArray = (next = traverse) => treeToArrayBy(next)(x => x) export let leaves = (next = traverse) => _.flow(treeToArray(next), _.reject(next)) -export let tree = (next = traverse) => ({ +export let treeLookup = (next = traverse, buildIteratee = _.identity) => (path, tree) => + _.reduce((tree, path) => _.find(buildIteratee(path), next(tree)), tree, path) + + +export let tree = (next = traverse, buildIteratee = _.identity) => ({ walk: walk(next), reduce: reduceTree(next), toArrayBy: treeToArrayBy(next), toArray: treeToArray(next), leaves: leaves(next), + lookup: treeLookup(next, buildIteratee) }) diff --git a/test/lens.spec.js b/test/lens.spec.js index b99cd5d4..1db58d4f 100644 --- a/test/lens.spec.js +++ b/test/lens.spec.js @@ -42,6 +42,16 @@ describe('Lens Functions', () => { l.set(5) expect(l.get()).to.equal(5) }) + it('lensProp deep', () => { + let l = f.lensProp('x.a', { + x: { + a: 1 + }, + }) + expect(l.get()).to.equal(1) + l.set(5) + expect(l.get()).to.equal(5) + }) it('lensOf', () => { let l = f.lensOf({ a: 1, diff --git a/test/tree.spec.js b/test/tree.spec.js index 64f612b0..4fbab223 100644 --- a/test/tree.spec.js +++ b/test/tree.spec.js @@ -131,4 +131,41 @@ describe('Tree Functions', () => { let tree = f.tree() expect(tree.toArray(x)).to.deep.equal([x, x.a, x.b, x.b.c]) }) + it('lookup', () => { + let x = { + a: 1, + items: [{ + a: 2, + items: [{ + a: 3 + }, { + a: 4, + b: 4 + }] + }, { + a: 5 + }] + } + let tree = f.tree(x => x.items) + + expect(tree.lookup([{a:2}, {a:4}], x)).to.deep.equal(x.items[0].items[1]) + }) + it('lookup with path', () => { + let x = { + a: '1', + items: [{ + a: '2', + items: [{ + a: '3' + }, { + a: '4', + b: 4 + }] + }, { + a: '5' + }] + } + let tree = f.tree(x => x.items, a => ({a})) + expect(tree.lookup(['2', '4'], x)).to.deep.equal(x.items[0].items[1]) + }) })