From c07785640a87fa23189b33e0bef8b3fc8fc12923 Mon Sep 17 00:00:00 2001 From: Barney Carroll Date: Tue, 2 Jan 2018 14:00:31 +0000 Subject: [PATCH] Version 2.0.0! (#7) * Fixes #6. Properties on targets can now be deleted declaratively. * API is now P, S, PS, D (was patch, scope, ps). Terse capital initials are more consistently identifiable, at a skim, as directives in arbitrary code structures. --- README.md | 41 +++++++++++---------- index.js | 37 +++++++++++-------- tests/index.js | 99 +++++++++++++++++++++++++++++++++++--------------- 3 files changed, 111 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 8ac9842..15c2c4f 100644 --- a/README.md +++ b/README.md @@ -6,22 +6,22 @@ Throw your rose-tinted [lenses](https://medium.com/javascript-inside/an-introduc # What -Patchinko exposes 3 functions: `patch`, `scope`, & `ps`. +Patchinko exposes 3 functions: `P`, `S`, & `PS`. -`patch` is like `Object.assign` - given `patch(target, input1, input2, etc)`, it consumes inputs left to right and copies their properties onto the supplied target. +`P` is like `Object.assign`: given `P(target, input1, input2, etc)`, it consumes inputs left to right and copies their properties onto the supplied target -*except that* +*…except that…* -If any target properties are instances of `scope(function)`, it will supply the scoped function with the target property for that key, and assign the result back to the target. +If any target properties are instances of `S(function)`, it will supply the scoped function with the target property for that key, and assign the result back to the target; if any target properties are `D`, it will delete the property of the same key on the target. -`ps([ target, ] input)` is a composition of `patch` & `scope`, for when you need to patch recursively. If you supply a `target`, the original value will be left untouched (useful for immutable patching). +`PS([ target, ] input)` is a composition of `P` & `S`, for when you need to patch recursively. If you supply a `target`, the original value will be left untouched (useful for immutable patching). # How The kitchen sink example: ```js -const {patch : p, scope : s, ps} = require('patchinko') +const {P, S, PS, D} = require('patchinko') // Some arbitrary structure const thing = { @@ -29,6 +29,8 @@ const thing = { fizz: 'buzz', + bish: 'bash', + utils: { mean: (...set) => set.reduce((a, b) => a + b) / set.length, @@ -42,16 +44,18 @@ const thing = { deep: { structure: ['lol'] }, - with: ['an', 'array', 'tacked'] + with: ['a', 'list', 'tacked', 'on'] } } // A deep patch -p(thing, { - foo: 'baz', // Change the value of foo +P(thing, { + foo: 'baz', // Change the value of `foo` + + bish: D, // Delete property `bish` - utils: ps({ // We want to patch a level deeper - fibonacci: s(function closure(definition){ // Memoize fibonacci + utils: PS({ // We want to patch a level deeper + fibonacci: S(function closure(definition){ // Memoize `fibonacci` var cache = {} return function override(x){ @@ -64,16 +68,16 @@ p(thing, { }) }), - stupidly: ps({ - deep: ps({ - structure: s(structure => + stupidly: PS({ + deep: PS({ + structure: S(structure => structure.concat('roflmao') // Why not ) }), - with: ps( + with: PS( [], {1: 'copy'} - ) // ['a', 'copy', 'tacked'], the original array is left untouched + ) // ['a', 'copy', 'tacked', 'on'] - the original array is left untouched }) }) ``` @@ -84,11 +88,8 @@ Observe that: * `utils.fibonacci` can safely be decorated (again, the rest of `utils` is unaffected) * `stupidly.deep.structure` can be modified, keeping its identity -`stupidly.deep.stucture` & `utils.fibonacci` show that any kind of structure can be modified or replaced at any kind of depth: `patch` is geared towards the common case of objects, but `scope` can deal with any type in whatever way necessary. You get closures for free so gnarly patch logic can be isolated at the point where it makes the most sense. - -```JS +`stupidly.deep.stucture` & `utils.fibonacci` show that any kind of structure can be modified or replaced at any kind of depth: `P` is geared towards the common case of objects, but `S` can deal with any type in whatever way necessary. You get closures for free so gnarly patch logic can be isolated at the point where it makes the most sense. -```` # Why diff --git a/index.js b/index.js index 3fa5f8a..e64e7be 100644 --- a/index.js +++ b/index.js @@ -1,35 +1,40 @@ -function patch(target){ +function P(target){ for(var i = 1; i < arguments.length; i++) for(var key in arguments[i]) if(arguments[i].hasOwnProperty(key)) - target[key] = ( - arguments[i][key] instanceof scope + arguments[i][key] == D + ? delete target[key] + : target[key] = + arguments[i][key] instanceof S ? arguments[i][key].apply(target[key]) : arguments[i][key] - ) return target } -function scope(closure){ - if(!(this instanceof scope)) - return new scope(closure) +function S(closure){ + if(!(this instanceof S)) + return new S(closure) this.apply = function(definition){ return closure(definition) } } -function ps(target, input){ - return arguments.length === 2 - ? new scope(function(definition){ - return patch(target, definition, input) - }) - : new scope(function(definition){ - return patch(definition, target) // `target` really is `input` in that case - }) +function PS(target, input){ + return new S( + arguments.length === 2 + ? function(definition){ + return P(target, definition, input) + } + : function(definition){ + return P(definition, target) + } + ) } +function D(){} + try { - module.exports = {patch: patch, scope: scope, ps: ps} + module.exports = {P: P, S: S, PS: PS, D: D} } catch(e) {} diff --git a/tests/index.js b/tests/index.js index cd079ac..5419652 100644 --- a/tests/index.js +++ b/tests/index.js @@ -1,34 +1,34 @@ const o = require('ospec') -const {patch, scope, ps} = require('../index.js') +const {P, S, PS, D} = require('../index.js') const I = x => x const A = f => x => f(x) -o('`scope`', () => { +o('`S`', () => { const unique = Symbol('unicum') o( - scope(I).apply(unique) + S(I).apply(unique) ).equals( A(I)(unique) ) ('is equivalent to an applicative combinator') o( - scope(I) instanceof scope + S(I) instanceof S ).equals( true ) - ('whose partial application is identifiable as a `scope`') + ('whose partial application is identifiable as a `S`') }) -o.spec('`patch`', () => { +o.spec('`P`', () => { o('consumes a target object & an input object', () => { - o(patch({}, {})) + o(P({}, {})) }) - o('is equivalent to `Object.assign` in the absence of `scope`', () => { + o('is equivalent to `Object.assign` in the absence of `S`', () => { const [factoryA, factoryB] = [ () => ({a:'foo', b:2, d: {bar: 'z'}, f: [3, 4]}), () => ({a:'baz', c:3, d: {fizz: 'z'}, f: 'buzz'}), @@ -37,31 +37,33 @@ o.spec('`patch`', () => { const [a, b] = [factoryA(), factoryB()] o( - patch(a, b) + P(a, b) ).equals( a ) ('preserves target identity') o( - patch(factoryA(), factoryB()) + P(factoryA(), factoryB()) ).deepEquals( Object.assign(factoryA(), factoryB()) ) ('copies properties of input onto target') }) - o.spec('with `scope`', () => { + o.spec('with `S`', () => { o('supplies the target\'s property value to the scoped function', () => { const unique = Symbol('unicum') let interception - patch( - {a: unique}, - {a: scope(received => { - interception = received - })} + P( + { a: unique }, + { + a: S(received => { + interception = received + }) + } ) o(interception).equals(unique) @@ -72,34 +74,71 @@ o.spec('`patch`', () => { const unique2 = Symbol('unicum2') o( - patch( - {a: unique1}, - {a: scope(I)} + P( + { a: unique1 }, + { a: S(I) } ).a ).equals( unique1 - ) + ) + o( - patch( - {a: unique1}, - {a: scope(() => unique2)} + P( + { a: unique1 }, + { a: S(() => unique2) } ).a ).equals( unique2 + ) + }) + }) + + o.spec('with `D`', () => { + o('deletes the target property with the same key', () => { + o( + P( + { a: 1, b: 2 }, + { a: D } + ) + ).deepEquals( + { b: 2 } ) }) + + o('assigns the product of any scoped closures to the target properties', () => { + const unique1 = Symbol('unicum1') + const unique2 = Symbol('unicum2') + + o( + P( + { a: unique1 }, + { a: S(I) } + ).a + ).equals( + unique1 + ) + + o( + P( + { a: unique1 }, + { a: S(() => unique2) } + ).a + ).equals( + unique2 + ) + }) }) }) -o.spec('`ps`', () => { - o('composes `patch` with `scope`, allowing recursion', () => { +o.spec('`PS`', () => { + o('composes `P` with `S`, allowing recursion', () => { const one = Symbol('one') const two = Symbol('two') let interception o( - patch( + P( { a: { b: one @@ -107,8 +146,8 @@ o.spec('`ps`', () => { }, { - a: ps({ - b: scope(received => { + a: PS({ + b: S(received => { interception = received return two @@ -129,12 +168,12 @@ o.spec('`ps`', () => { }) o('accepts a custom target', () => { o( - patch( + P( { a: [1, 2] }, { - a: ps( + a: PS( [], { 1: 3