Skip to content

Commit

Permalink
✨ Slices get
Browse files Browse the repository at this point in the history
  • Loading branch information
nlepage committed Jul 29, 2019
1 parent 85f8c13 commit 41f4858
Show file tree
Hide file tree
Showing 12 changed files with 109 additions and 113 deletions.
18 changes: 9 additions & 9 deletions packages/immutadot/src/core/apply.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 0 additions & 4 deletions packages/immutadot/src/core/apply.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
12 changes: 3 additions & 9 deletions packages/immutadot/src/core/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
2 changes: 1 addition & 1 deletion packages/immutadot/src/core/get.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]])
})
Expand Down
1 change: 1 addition & 0 deletions packages/immutadot/src/core/set.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
3 changes: 2 additions & 1 deletion packages/immutadot/src/core/unset.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
25 changes: 1 addition & 24 deletions packages/immutadot/src/nav/nav.js
Original file line number Diff line number Diff line change
@@ -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'
32 changes: 16 additions & 16 deletions packages/immutadot/src/nav/nav.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -262,22 +262,22 @@ 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', () => {
immutaTest(
{ 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
},
Expand All @@ -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
},
Expand All @@ -301,34 +301,34 @@ 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
},
)
})

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(
Expand All @@ -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: [
Expand Down
2 changes: 1 addition & 1 deletion packages/immutadot/src/nav2/indexNav.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const unset = (pIndex, next) => () => {
})
}

export const propNav = makeNav({
export const indexNav = makeNav({
update,
get,
unset,
Expand Down
10 changes: 7 additions & 3 deletions packages/immutadot/src/nav2/nav.js
Original file line number Diff line number Diff line change
@@ -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]) => {
Expand Down
45 changes: 0 additions & 45 deletions packages/immutadot/src/nav2/nav.spec.js

This file was deleted.

68 changes: 68 additions & 0 deletions packages/immutadot/src/nav2/sliceNav.js
Original file line number Diff line number Diff line change
@@ -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,
})

0 comments on commit 41f4858

Please sign in to comment.