From 41f48585ee16bdbd9744956bb790062fb53a69c7 Mon Sep 17 00:00:00 2001 From: Nicolas Lepage <19571875+nlepage@users.noreply.github.com> Date: Fri, 26 Jul 2019 16:24:38 +0200 Subject: [PATCH] :sparkles: Slices get --- packages/immutadot/src/core/apply.js | 18 +++--- packages/immutadot/src/core/apply.spec.js | 4 -- packages/immutadot/src/core/get.js | 12 +--- packages/immutadot/src/core/get.spec.js | 2 +- packages/immutadot/src/core/set.js | 1 + packages/immutadot/src/core/unset.js | 3 +- packages/immutadot/src/nav/nav.js | 25 +-------- packages/immutadot/src/nav/nav.spec.js | 32 +++++------ packages/immutadot/src/nav2/indexNav.js | 2 +- packages/immutadot/src/nav2/nav.js | 10 +++- packages/immutadot/src/nav2/nav.spec.js | 45 --------------- packages/immutadot/src/nav2/sliceNav.js | 68 +++++++++++++++++++++++ 12 files changed, 109 insertions(+), 113 deletions(-) delete mode 100644 packages/immutadot/src/nav2/nav.spec.js create mode 100644 packages/immutadot/src/nav2/sliceNav.js diff --git a/packages/immutadot/src/core/apply.js b/packages/immutadot/src/core/apply.js index 56425e77..0af0282b 100644 --- a/packages/immutadot/src/core/apply.js +++ b/packages/immutadot/src/core/apply.js @@ -14,42 +14,42 @@ function apply(fn, { arity = fn.length, fixedArity = false, curried = true, lazy switch (arity) { case 0: case 1: - appliedFn = (obj, path) => nav(toPath(path))(obj).update(fn) + appliedFn = (obj, path) => nav(toPath(path)).update(fn)(obj) break case 2: - appliedFn = (obj, path, arg) => nav(toPath(path))(obj).update( + appliedFn = (obj, path, arg) => nav(toPath(path)).update( value => fn(value, resolveArg(arg, obj)), - ) + )(obj) break case 3: - appliedFn = (obj, path, arg1, arg2) => nav(toPath(path))(obj).update( + appliedFn = (obj, path, arg1, arg2) => nav(toPath(path)).update( value => fn( value, resolveArg(arg1, obj), resolveArg(arg2, obj), ), - ) + )(obj) break case 4: - appliedFn = (obj, path, arg1, arg2, arg3) => nav(toPath(path))(obj).update( + appliedFn = (obj, path, arg1, arg2, arg3) => nav(toPath(path)).update( value => fn( value, resolveArg(arg1, obj), resolveArg(arg2, obj), resolveArg(arg3, obj), ), - ) + )(obj) break } } if (!appliedFn) { - appliedFn = (obj, path, ...args) => nav(toPath(path))(obj).update( + appliedFn = (obj, path, ...args) => nav(toPath(path)).update( value => fn( value, ...args.map(arg => resolveArg(arg, obj)), ), - ) + )(obj) } if (curried) { diff --git a/packages/immutadot/src/core/apply.spec.js b/packages/immutadot/src/core/apply.spec.js index f627a146..4686f8f9 100644 --- a/packages/immutadot/src/core/apply.spec.js +++ b/packages/immutadot/src/core/apply.spec.js @@ -238,10 +238,6 @@ describe('path.apply', () => { }) }) - it('should throw an explicit error when en empty path is given as parameter', () => { - expect(() => inc({}, '')).toThrowError('path should not be empty') - }) - it('should support curried first arg', () => { immutaTest({ nested: { prop: 5 }, diff --git a/packages/immutadot/src/core/get.js b/packages/immutadot/src/core/get.js index 080a2c24..71523442 100644 --- a/packages/immutadot/src/core/get.js +++ b/packages/immutadot/src/core/get.js @@ -13,23 +13,17 @@ const isGetter = Symbol('isGetter') * @since 1.0.0 */ function get(...args) { - if (args.length >= 2) - return _get(...args) + if (args.length >= 2) return nav(toPath(args[1])).get()(args[0]) - const getter = obj => _get(obj, ...args) + const getter = nav(toPath(args[0])).get() getter[isGetter] = true return getter } -function _get(obj, path) { - return nav(toPath(path))(obj).get() -} - function resolveGetter(value, obj) { if (value && value[isGetter]) return value(obj) return value } -// FIXME stop exporting isGetter -export { get, isGetter, resolveGetter } +export { get, resolveGetter } diff --git a/packages/immutadot/src/core/get.spec.js b/packages/immutadot/src/core/get.spec.js index c07704f2..3ae2b830 100644 --- a/packages/immutadot/src/core/get.spec.js +++ b/packages/immutadot/src/core/get.spec.js @@ -14,7 +14,7 @@ describe('core.get', () => { expect(get(obj, 'nested2.arr[0].val')).toBe('arrVal1') }) - it('should get multiple props', () => { + it('should get slices of nested props', () => { expect(get(obj, 'nested2.arr[:].val')).toEqual(['arrVal1', 'arrVal2']) expect(get(obj, 'nested3[:][:].val')).toEqual([[1, 2], [3, 4]]) }) diff --git a/packages/immutadot/src/core/set.js b/packages/immutadot/src/core/set.js index 1e0ad715..fe7932dd 100644 --- a/packages/immutadot/src/core/set.js +++ b/packages/immutadot/src/core/set.js @@ -10,6 +10,7 @@ import { apply } from './apply' * @example set({ nested: { prop: 'old' } }, 'nested.prop', 'new') // => { nested: { prop: 'new' } } * @since 1.0.0 */ +// FIXME do not use apply here ? const set = apply((_, value) => value, { fixedArity: true }) export { set } diff --git a/packages/immutadot/src/core/unset.js b/packages/immutadot/src/core/unset.js index e89bc1e0..7d7ee6e1 100644 --- a/packages/immutadot/src/core/unset.js +++ b/packages/immutadot/src/core/unset.js @@ -12,6 +12,7 @@ import { toPath } from 'immutadot-parser' * @example unset({ nested: { prop: 'value' } }, 'nested.prop') // => { nested: {} } * @since 1.0.0 */ -const unset = curry((obj, path) => nav(toPath(path))(obj).unset(), { fixedArity: true }) +// FIXME do not use curry here ? +const unset = curry((obj, path) => nav(toPath(path)).unset()(obj), { fixedArity: true }) export { unset } diff --git a/packages/immutadot/src/nav/nav.js b/packages/immutadot/src/nav/nav.js index 7ef96bfe..8beefee5 100644 --- a/packages/immutadot/src/nav/nav.js +++ b/packages/immutadot/src/nav/nav.js @@ -1,24 +1 @@ -import { NavType } from 'immutadot-parser' -import { finalNav } from './finalNav' -import { indexNav } from './indexNav' -import { propNav } from './propNav' -import { propsNav } from './propsNav' -import { sliceNav } from './sliceNav' - -export function nav(path) { - if (path.length === 0) throw new TypeError('path should not be empty') - - return path.reduceRight((next, [type, value]) => toNav(type)(value, next), finalNav) -} - -function toNav(type) { - switch (type) { - case NavType.allProps: - case NavType.list: - return propsNav - case NavType.index: return indexNav - case NavType.prop: return propNav - case NavType.slice: return sliceNav - default: throw TypeError(type) - } -} +export { nav } from '../nav2/nav' diff --git a/packages/immutadot/src/nav/nav.spec.js b/packages/immutadot/src/nav/nav.spec.js index 3a4ade99..a706827a 100644 --- a/packages/immutadot/src/nav/nav.spec.js +++ b/packages/immutadot/src/nav/nav.spec.js @@ -13,7 +13,7 @@ describe('nav.nav', () => { } function uncurriedInc(obj, path, ...args) { - return nav(toPath(path))(obj).update(v => incV(v, ...args)) + return nav(toPath(path)).update(v => incV(v, ...args))(obj) } function curriedInc(path, ...args) { @@ -224,7 +224,7 @@ describe('nav.nav', () => { }) it('should throw an explicit error when en empty path is given as parameter', () => { - expect(() => inc({}, '')).toThrowError('path should not be empty') + expect(() => inc({}, '')).toThrowError('Path should not be empty') }) it('should support curried first arg', () => { @@ -262,14 +262,14 @@ describe('nav.nav', () => { }) it('should get a nested prop', () => { - expect(nav(toPath('nested.prop'))({ nested: { prop: 'foo' } }).get()).toBe('foo') + expect(nav(toPath('nested.prop')).get()({ nested: { prop: 'foo' } })).toBe('foo') }) it('should get a list of props', () => { - expect(nav(toPath('nested.{prop1,prop2}'))({ nested: { prop1: 'foo', - prop2: 'bar' } }).get()).toEqual(['foo', 'bar']) - expect(nav(toPath('nested.{*}'))({ nested: { prop1: 'foo', - prop2: 'bar' } }).get()).toEqual(['foo', 'bar']) + expect(nav(toPath('nested.{prop1,prop2}')).get()({ nested: { prop1: 'foo', + prop2: 'bar' } })).toEqual(['foo', 'bar']) + expect(nav(toPath('nested.{*}')).get()({ nested: { prop1: 'foo', + prop2: 'bar' } })).toEqual(['foo', 'bar']) }) it('should set a nested prop', () => { @@ -277,7 +277,7 @@ describe('nav.nav', () => { { nested: { prop: 'foo' } }, ['nested.prop'], (input, [path]) => { - const output = nav(toPath(path))(input).update(() => 'bar') + const output = nav(toPath(path)).update(() => 'bar')(input) expect(output).toEqual({ nested: { prop: 'bar' } }) return output }, @@ -289,7 +289,7 @@ describe('nav.nav', () => { { nested: { prop: 'foo' } }, ['nested.prop'], (input, [path]) => { - const output = nav(toPath(path))(input).update(value => value.toUpperCase()) + const output = nav(toPath(path)).update(value => value.toUpperCase())(input) expect(output).toEqual({ nested: { prop: 'FOO' } }) return output }, @@ -301,7 +301,7 @@ describe('nav.nav', () => { {}, ['nested.prop.0', 'nested.prop.1'], input => { - const output = nav(toPath('nested.prop[1]'))(input).update(() => 'foo') + const output = nav(toPath('nested.prop[1]')).update(() => 'foo')(input) expect(output).toEqual({ nested: { prop: [undefined, 'foo'] } }) return output }, @@ -309,26 +309,26 @@ describe('nav.nav', () => { }) it('should get a slice', () => { - expect(nav(toPath('nested.prop[:].val'))({ + expect(nav(toPath('nested.prop[:].val')).get()({ nested: { prop: [ { val: 'foo' }, { val: 'bar' }, ], }, - }).get()).toEqual(['foo', 'bar']) - expect(nav(toPath('nested.prop[::-1].val'))({ + })).toEqual(['foo', 'bar']) + expect(nav(toPath('nested.prop[::-1].val')).get()({ nested: { prop: [ { val: 'foo' }, { val: 'bar' }, ], }, - }).get()).toEqual(['bar', 'foo']) + })).toEqual(['bar', 'foo']) }) it('should get a negative array index', () => { - expect(nav(toPath('nested.prop[-3]'))({ nested: { prop: [0, 1, 2, 3, 4] } }).get()).toBe(2) + expect(nav(toPath('nested.prop[-3]')).get()({ nested: { prop: [0, 1, 2, 3, 4] } })).toBe(2) }) it('should update a slice', () => immutaTest( @@ -343,7 +343,7 @@ describe('nav.nav', () => { }, ['nested.prop.1.val', 'nested.prop.2.val'], input => { - const output = nav(toPath('nested.prop[-2:].val'))(input).update(value => value.toUpperCase()) + const output = nav(toPath('nested.prop[-2:].val')).update(value => value.toUpperCase())(input) expect(output).toEqual({ nested: { prop: [ diff --git a/packages/immutadot/src/nav2/indexNav.js b/packages/immutadot/src/nav2/indexNav.js index ded1c574..5066340a 100644 --- a/packages/immutadot/src/nav2/indexNav.js +++ b/packages/immutadot/src/nav2/indexNav.js @@ -44,7 +44,7 @@ const unset = (pIndex, next) => () => { }) } -export const propNav = makeNav({ +export const indexNav = makeNav({ update, get, unset, diff --git a/packages/immutadot/src/nav2/nav.js b/packages/immutadot/src/nav2/nav.js index 34dc86bd..c363bce4 100644 --- a/packages/immutadot/src/nav2/nav.js +++ b/packages/immutadot/src/nav2/nav.js @@ -1,12 +1,16 @@ +import { NavType } from 'immutadot-parser' import { finalNav } from './finalNav' import { indexNav } from './indexNav' import { propNav } from './propNav' import { propsNav } from './propsNav' +import { sliceNav } from './sliceNav' const navs = { - prop: propNav, - props: propsNav, - index: indexNav, + [NavType.prop]: propNav, + [NavType.list]: propsNav, + [NavType.allProps]: propsNav, + [NavType.index]: indexNav, + [NavType.slice]: sliceNav, } const toNav = ([type, params]) => { diff --git a/packages/immutadot/src/nav2/nav.spec.js b/packages/immutadot/src/nav2/nav.spec.js deleted file mode 100644 index dc04dfc1..00000000 --- a/packages/immutadot/src/nav2/nav.spec.js +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-env jest */ -import { immutaTest } from 'test.utils' -import { nav } from './nav' - -describe('nav.nav', () => { - it('should update a nested prop', () => immutaTest( - { - nested: { prop: 'foo' }, - other: { prop: 'baz' }, - }, - ['nested.prop'], - input => { - const output = nav([['prop', 'nested'], ['prop', 'prop']]).update(() => 'bar')(input) - expect(output).toEqual({ - nested: { prop: 'bar' }, - other: { prop: 'baz' }, - }) - return output - }, - )) - - it('should get a nested prop', () => { - const input = { - nested: { prop: 'foo' }, - other: { prop: 'baz' }, - } - expect(nav([['prop', 'nested'], ['prop', 'prop']]).get()(input)).toEqual('foo') - }) - - it('should unset a nested prop', () => immutaTest( - { - nested: { prop: 'foo' }, - other: { prop: 'bar' }, - }, - ['nested.prop'], - input => { - const output = nav([['prop', 'nested'], ['prop', 'prop']]).unset()(input) - expect(output).toEqual({ - nested: {}, - other: { prop: 'bar' }, - }) - return output - }, - )) -}) diff --git a/packages/immutadot/src/nav2/sliceNav.js b/packages/immutadot/src/nav2/sliceNav.js new file mode 100644 index 00000000..841e9dc9 --- /dev/null +++ b/packages/immutadot/src/nav2/sliceNav.js @@ -0,0 +1,68 @@ +import { length as getLength, isNil } from 'util/lang' +import { makeNav } from './makeNav' +import { onCopy } from './arrayNav' + +const resolveIndex = (index, length) => index >= 0 ? index : Math.max(length + index, 0) + +const resolveStart = (pStart, length, step) => { + if (length === 0) return 0 + if (pStart === undefined) return step > 0 ? 0 : length - 1 + return resolveIndex(pStart, length) +} + +const resolveEnd = (pEnd, length, step) => { + if (length === 0) return 0 + if (pEnd === undefined) return step > 0 ? length : -1 + return resolveIndex(pEnd, length) +} + +const update = ([pStart, pEnd, pStep], next) => updater => { + const nextUpdater = next(updater) + const step = pStep === undefined ? 1 : pStep + return onCopy((newValue, value) => { + const length = getLength(value) + const start = resolveStart(pStart, length, step) + const end = resolveEnd(pEnd, length, step) + if (step > 0) { + if (end <= start) return // TODO avoid useless copy + if (isNil(value)) + for (let i = start; i < end; i += step) newValue[i] = nextUpdater(undefined) + else + for (let i = start; i < end; i += step) newValue[i] = nextUpdater(value[i]) + } + if (end >= start) return // TODO avoid useless copy + if (isNil(value)) + for (let i = start; i > end; i += step) newValue[i] = nextUpdater(undefined) + else + for (let i = start; i > end; i += step) newValue[i] = nextUpdater(value[i]) + }, true) +} + +const get = ([pStart, pEnd, pStep], next) => () => { + const nextGetter = next() + const step = pStep === undefined ? 1 : pStep + return value => { + const length = getLength(value) + const start = resolveStart(pStart, length, step) + const end = resolveEnd(pEnd, length, step) + if (isNil(value)) return [] + let range + if (step > 0) { + if (end <= start) return [] + range = (function* () { + for (let i = start; i < end; i += step) yield i + }()) + } else { + if (end >= start) return [] + range = (function* () { + for (let i = start; i > end; i += step) yield i + }()) + } + return Array.from(range, i => nextGetter(value[i])) + } +} + +export const sliceNav = makeNav({ + update, + get, +})