Comparing version 0.1.1 to 0.1.2
415
index.js
@@ -1,206 +0,257 @@ | ||
({ define: typeof define === "function" | ||
? define // browser | ||
: function(F) { F(require,exports,module); } }). // Node.js | ||
define(function (require, exports, module) { | ||
"use strict"; | ||
;(function(undefined) { | ||
"use strict"; | ||
var util = require('util'); | ||
var $scope | ||
, conflict, conflictResolution = []; | ||
if (typeof global == 'object' && global) { | ||
$scope = global; | ||
} else if (typeof window !== 'undefined'){ | ||
$scope = window; | ||
} else { | ||
$scope = {}; | ||
} | ||
conflict = $scope.DeepDiff; | ||
if (conflict) { | ||
conflictResolution.push( | ||
function() { | ||
if ($scope.DeepDiff === accumulateDiff) { | ||
$scope.DeepDiff = conflict; | ||
conflict = undefined; | ||
} | ||
}); | ||
} | ||
function Diff(kind, path) { | ||
Object.defineProperty(this, 'kind', { value: kind, enumerable: true }); | ||
if (path && path.length) { | ||
Object.defineProperty(this, 'path', { value: path, enumerable: true }); | ||
} | ||
} | ||
function DiffEdit(path, origin, value) { | ||
DiffEdit.super_.call(this, 'E', path); | ||
// nodejs compatible on server side and in the browser. | ||
function inherits(ctor, superCtor) { | ||
ctor.super_ = superCtor; | ||
ctor.prototype = Object.create(superCtor.prototype, { | ||
constructor: { | ||
value: ctor, | ||
enumerable: false, | ||
writable: true, | ||
configurable: true | ||
} | ||
}); | ||
} | ||
Object.defineProperty(this, 'lhs', { value: origin, enumerable: true }); | ||
Object.defineProperty(this, 'rhs', { value: value, enumerable: true }); | ||
} | ||
util.inherits(DiffEdit, Diff); | ||
function DiffNew(path, value) { | ||
DiffNew.super_.call(this, 'N', path); | ||
function Diff(kind, path) { | ||
Object.defineProperty(this, 'kind', { value: kind, enumerable: true }); | ||
if (path && path.length) { | ||
Object.defineProperty(this, 'path', { value: path, enumerable: true }); | ||
} | ||
} | ||
Object.defineProperty(this, 'rhs', { value: value, enumerable: true }); | ||
} | ||
util.inherits(DiffNew, Diff); | ||
function DiffDeleted(path, value) { | ||
DiffDeleted.super_.call(this, 'D', path); | ||
function DiffEdit(path, origin, value) { | ||
DiffEdit.super_.call(this, 'E', path); | ||
Object.defineProperty(this, 'lhs', { value: origin, enumerable: true }); | ||
Object.defineProperty(this, 'rhs', { value: value, enumerable: true }); | ||
} | ||
inherits(DiffEdit, Diff); | ||
Object.defineProperty(this, 'lhs', { value: value, enumerable: true }); | ||
} | ||
util.inherits(DiffDeleted, Diff); | ||
function DiffArray(path, index, item) { | ||
DiffArray.super_.call(this, 'A', path); | ||
function DiffNew(path, value) { | ||
DiffNew.super_.call(this, 'N', path); | ||
Object.defineProperty(this, 'rhs', { value: value, enumerable: true }); | ||
} | ||
inherits(DiffNew, Diff); | ||
Object.defineProperty(this, 'index', { value: index, enumerable: true }); | ||
Object.defineProperty(this, 'item', { value: item, enumerable: true }); | ||
} | ||
util.inherits(DiffArray, Diff); | ||
function DiffDeleted(path, value) { | ||
DiffDeleted.super_.call(this, 'D', path); | ||
Object.defineProperty(this, 'lhs', { value: value, enumerable: true }); | ||
} | ||
inherits(DiffDeleted, Diff); | ||
function arrayRemove(arr, from, to) { | ||
var rest = arr.slice((to || from) + 1 || arr.length); | ||
arr.length = from < 0 ? arr.length + from : from; | ||
arr.push.apply(arr, rest); | ||
return arr; | ||
} | ||
function DiffArray(path, index, item) { | ||
DiffArray.super_.call(this, 'A', path); | ||
Object.defineProperty(this, 'index', { value: index, enumerable: true }); | ||
Object.defineProperty(this, 'item', { value: item, enumerable: true }); | ||
} | ||
inherits(DiffArray, Diff); | ||
var recordDifferences; | ||
function arrayRemove(arr, from, to) { | ||
var rest = arr.slice((to || from) + 1 || arr.length); | ||
arr.length = from < 0 ? arr.length + from : from; | ||
arr.push.apply(arr, rest); | ||
return arr; | ||
} | ||
function deepDiff(lhs, rhs, changes, path, key, stack) { | ||
path = path || []; | ||
var currentPath = path.slice(0); | ||
if (key) { currentPath.push(key); } | ||
var ltype = typeof lhs; | ||
var rtype = typeof rhs; | ||
if (ltype === 'undefined') { | ||
if (rtype !== 'undefined') { | ||
changes(new DiffNew(currentPath, rhs )); | ||
} | ||
} else if (rtype === 'undefined') { | ||
changes(new DiffDeleted(currentPath, lhs)); | ||
} else if (ltype !== rtype) { | ||
changes(new DiffEdit(currentPath, lhs, rhs)); | ||
} else if (ltype === 'object' && lhs != null) { | ||
stack = stack || []; | ||
if (stack.indexOf(lhs) < 0) { | ||
stack.push(lhs); | ||
if (Array.isArray(lhs)) { | ||
var i, ea = function(d) { | ||
changes(new DiffArray(currentPath, i, d)); | ||
}; | ||
for(i = 0; i < lhs.length; i++) { | ||
if (i >= rhs.length) { | ||
changes(new DiffArray(currentPath, i, new DiffDeleted(undefined, lhs[i]))); | ||
} else { | ||
deepDiff(lhs[i], rhs[i], ea, [], null, stack); | ||
} | ||
} | ||
while(i < rhs.length) { | ||
changes(new DiffArray(currentPath, i, new DiffNew(undefined, rhs[i++]))); | ||
} | ||
} else { | ||
var akeys = Object.keys(lhs); | ||
var pkeys = Object.keys(rhs); | ||
akeys.forEach(function(k) { | ||
var i = pkeys.indexOf(k); | ||
if (i >= 0) { | ||
deepDiff(lhs[k], rhs[k], changes, currentPath, k, stack); | ||
pkeys = arrayRemove(pkeys, i); | ||
} else { | ||
deepDiff(lhs[k], undefined, changes, currentPath, k, stack); | ||
} | ||
}); | ||
pkeys.forEach(function(k) { | ||
deepDiff(undefined, rhs[k], changes, currentPath, k, stack); | ||
}); | ||
} | ||
stack.length = stack.length - 1; | ||
} | ||
} else if (lhs !== rhs) { | ||
changes(new DiffEdit(currentPath, lhs, rhs)); | ||
function deepDiff(lhs, rhs, changes, path, key, stack) { | ||
path = path || []; | ||
var currentPath = path.slice(0); | ||
if (key) { currentPath.push(key); } | ||
var ltype = typeof lhs; | ||
var rtype = typeof rhs; | ||
if (ltype === 'undefined') { | ||
if (rtype !== 'undefined') { | ||
changes(new DiffNew(currentPath, rhs )); | ||
} | ||
} else if (rtype === 'undefined') { | ||
changes(new DiffDeleted(currentPath, lhs)); | ||
} else if (ltype !== rtype) { | ||
changes(new DiffEdit(currentPath, lhs, rhs)); | ||
} else if (ltype === 'object' && lhs != null) { | ||
stack = stack || []; | ||
if (stack.indexOf(lhs) < 0) { | ||
stack.push(lhs); | ||
if (Array.isArray(lhs)) { | ||
var i | ||
, len = lhs.length | ||
, ea = function(d) { | ||
changes(new DiffArray(currentPath, i, d)); | ||
}; | ||
for(i = 0; i < lhs.length; i++) { | ||
if (i >= rhs.length) { | ||
changes(new DiffArray(currentPath, i, new DiffDeleted(undefined, lhs[i]))); | ||
} else { | ||
deepDiff(lhs[i], rhs[i], ea, [], null, stack); | ||
} | ||
} | ||
while(i < rhs.length) { | ||
changes(new DiffArray(currentPath, i, new DiffNew(undefined, rhs[i++]))); | ||
} | ||
} else { | ||
var akeys = Object.keys(lhs); | ||
var pkeys = Object.keys(rhs); | ||
akeys.forEach(function(k) { | ||
var i = pkeys.indexOf(k); | ||
if (i >= 0) { | ||
deepDiff(lhs[k], rhs[k], changes, currentPath, k, stack); | ||
pkeys = arrayRemove(pkeys, i); | ||
} else { | ||
deepDiff(lhs[k], undefined, changes, currentPath, k, stack); | ||
} | ||
}); | ||
pkeys.forEach(function(k) { | ||
deepDiff(undefined, rhs[k], changes, currentPath, k, stack); | ||
}); | ||
} | ||
stack.length = stack.length - 1; | ||
} | ||
} else if (lhs !== rhs) { | ||
changes(new DiffEdit(currentPath, lhs, rhs)); | ||
} | ||
} | ||
function accumulateDiff(lhs, rhs, accum) { | ||
accum = accum || []; | ||
deepDiff(lhs, rhs, function(diff) { | ||
if (diff) { | ||
accum.push(diff); | ||
} | ||
}); | ||
return (accum.length) ? accum : undefined; | ||
} | ||
function applyArrayChange(arr, index, change) { | ||
if (change.path && change.path.length) { | ||
// the structure of the object at the index has changed... | ||
var it = arr[index], i, u = change.path.length - 1; | ||
for(i = 0; i < u; i++){ | ||
it = it[change.path[i]]; | ||
} | ||
switch(change.kind) { | ||
case 'A': | ||
// Array was modified... | ||
// it will be an array... | ||
applyArrayChange(it, change.index, change.item); | ||
break; | ||
case 'D': | ||
// Item was deleted... | ||
delete it[change.path[i]]; | ||
break; | ||
case 'E': | ||
case 'N': | ||
// Item was edited or is new... | ||
it[change.path[i]] = change.rhs; | ||
break; | ||
} | ||
} else { | ||
// the array item is different... | ||
switch(change.kind) { | ||
case 'A': | ||
// Array was modified... | ||
// it will be an array... | ||
applyArrayChange(arr[index], change.index, change.item); | ||
break; | ||
case 'D': | ||
// Item was deleted... | ||
arr = arrayRemove(arr, index); | ||
break; | ||
case 'E': | ||
case 'N': | ||
// Item was edited or is new... | ||
arr[index] = change.rhs; | ||
break; | ||
} | ||
} | ||
return arr; | ||
} | ||
function accumulateDiff(lhs, rhs, accum) { | ||
accum = accum || []; | ||
deepDiff(lhs, rhs, function(diff) { | ||
if (diff) { | ||
accum.push(diff); | ||
} | ||
}); | ||
return (accum.length) ? accum : undefined; | ||
function applyChange(target, source, change) { | ||
if (!(change instanceof Diff)) { | ||
throw new TypeError('[Object] change must be instanceof Diff'); | ||
} | ||
function applyArrayChange(arr, index, change) { | ||
if (change.path && change.path.length) { | ||
// the structure of the object at the index has changed... | ||
var it = arr[index], i, u = change.path.length - 1; | ||
for(i = 0; i < u; i++){ | ||
it = it[change.path[i]]; | ||
if (target && source && change) { | ||
var it = target, i, u; | ||
u = change.path.length - 1; | ||
for(i = 0; i < u; i++){ | ||
if (typeof it[change.path[i]] === 'undefined') { | ||
it[change.path[i]] = {}; | ||
} | ||
switch(change.kind) { | ||
case 'A': | ||
// Array was modified... | ||
// it will be an array... | ||
applyArrayChange(it, change.index, change.item); | ||
break; | ||
case 'D': | ||
// Item was deleted... | ||
delete it[change.path[i]]; | ||
break; | ||
case 'E': | ||
case 'N': | ||
// Item was edited or is new... | ||
it[change.path[i]] = change.rhs; | ||
break; | ||
it = it[change.path[i]]; | ||
} | ||
switch(change.kind) { | ||
case 'A': | ||
// Array was modified... | ||
// it will be an array... | ||
applyArrayChange(it[change.path[i]], change.index, change.item); | ||
break; | ||
case 'D': | ||
// Item was deleted... | ||
delete it[change.path[i]]; | ||
break; | ||
case 'E': | ||
case 'N': | ||
// Item was edited or is new... | ||
it[change.path[i]] = change.rhs; | ||
break; | ||
} | ||
} else { | ||
// the array item is different... | ||
switch(change.kind) { | ||
case 'A': | ||
// Array was modified... | ||
// it will be an array... | ||
applyArrayChange(arr[index], change.index, change.item); | ||
break; | ||
case 'D': | ||
// Item was deleted... | ||
arr = arrayRemove(arr, index); | ||
break; | ||
case 'E': | ||
case 'N': | ||
// Item was edited or is new... | ||
arr[index] = change.rhs; | ||
break; | ||
} | ||
} | ||
return arr; | ||
} | ||
function applyChange(target, source, change) { | ||
if (!(change instanceof Diff)) { | ||
throw new TypeError('[Object] change must be instanceof Diff'); | ||
} | ||
if (target && source && change) { | ||
var it = target, i, u; | ||
u = change.path.length - 1; | ||
for(i = 0; i < u; i++){ | ||
it = it[change.path[i]]; | ||
function applyDiff(target, source, filter) { | ||
if (target && source) { | ||
var onChange = function(change) { | ||
if (!filter || filter(target, source, change)) { | ||
applyChange(target, source, change); | ||
} | ||
switch(change.kind) { | ||
case 'A': | ||
// Array was modified... | ||
// it will be an array... | ||
applyArrayChange(it[change.path[i]], change.index, change.item); | ||
break; | ||
case 'D': | ||
// Item was deleted... | ||
delete it[change.path[i]]; | ||
break; | ||
case 'E': | ||
case 'N': | ||
// Item was edited or is new... | ||
it[change.path[i]] = change.rhs; | ||
break; | ||
} | ||
} | ||
}; | ||
deepDiff(target, source, onChange); | ||
} | ||
} | ||
function applyDiff(target, source, filter) { | ||
if (target && source) { | ||
var onChange = function(change) { | ||
if (!filter || filter(target, source, change)) { | ||
applyChange(target, source, change); | ||
} | ||
}; | ||
deepDiff(target, source, onChange); | ||
} | ||
Object.defineProperties(accumulateDiff, { | ||
diff: { value: accumulateDiff, enumerable:true }, | ||
observableDiff: { value: deepDiff, enumerable:true }, | ||
applyDiff: { value: applyDiff, enumerable:true }, | ||
applyChange: { value: applyChange, enumerable:true }, | ||
isConflict: { get: function() { return 'undefined' !== typeof conflict; }, enumerable: true }, | ||
noConflict: { | ||
value: function () { | ||
if (conflictResolution) { | ||
conflictResolution.forEach(function (it) { it(); }); | ||
conflictResolution = null; | ||
} | ||
return accumulateDiff; | ||
}, | ||
enumerable: true | ||
} | ||
exports.diff = accumulateDiff; | ||
exports.observableDiff = deepDiff; | ||
exports.applyDiff = applyDiff; | ||
exports.applyChange = applyChange; | ||
}); | ||
if (typeof module != 'undefined' && module && typeof exports == 'object' && exports && module.exports === exports) { | ||
module.exports = accumulateDiff; // nodejs | ||
} else { | ||
$scope.DeepDiff = accumulateDiff; // other... browser? | ||
} | ||
}()); | ||
{ | ||
"name": "deep-diff", | ||
"description": "Node.js module providing utility functions for working with the structural differences between objects.", | ||
"version": "0.1.1", | ||
"version": "0.1.2", | ||
"keywords": [ | ||
@@ -11,6 +11,6 @@ "diff", | ||
], | ||
"author": { | ||
"name": "Phillip Clark", | ||
"email": "phillip@flitbit.org" | ||
}, | ||
"author": "Phillip Clark <phillip@flitbit.com>", | ||
"contributors": [ | ||
"SocalNick" | ||
], | ||
"repository": { "type": "git", "url": "git://github.com/flitbit/diff.git" }, | ||
@@ -17,0 +17,0 @@ "main": "./index.js", |
@@ -1,2 +0,2 @@ | ||
# deep-diff | ||
# deep-diff [![Build Status](https://travis-ci.org/flitbit/diff.png)](http://travis-ci.org/flitbit/diff) | ||
@@ -17,15 +17,36 @@ **deep-diff** is a javascript/node.js module providing utility functions for working with the structural differences between objects. | ||
## Testing | ||
Tests are written using [vows](http://vowsjs.org/) & [should.js](https://github.com/visionmedia/should.js/) (you may need to install them). If you've installed in a development environment you can use npm to run the tests. | ||
## Tests | ||
Tests use [mocha](http://visionmedia.github.io/mocha/) and [expect.js](https://github.com/LearnBoost/expect.js/), so if you clone the [github repository](https://github.com/flitbit/json-ptr) you'll need to run: | ||
```bash | ||
npm install | ||
``` | ||
npm test deep-diff | ||
... followed by ... | ||
```bash | ||
npm test | ||
``` | ||
If you intend to run the examples you'll also need [extend](https://github.com/justmoon/node-extend) and [lodash](https://github.com/bestiejs/lodash). | ||
... or ... | ||
## Warning! | ||
```bash | ||
mocha -R spec | ||
``` | ||
I've only had time to verify its behavior in node.js. If you're working in a browser you're on your own for now. | ||
### Importing | ||
**nodejs** | ||
```javascript | ||
var deep = require('deep-diff') | ||
``` | ||
**browser** | ||
```html | ||
<script src="deep-diff-0.1.2.min.js"></script> | ||
``` | ||
> In a browser, `deep-diff` defines a global variable `DeepDiff`. If there is a conflict in the global namesapce you can restore the conflicting definition and assign `deep-diff` to another variable like this: `var deep = DeepDiff.noConflict();`. | ||
## Simple Examples | ||
@@ -58,3 +79,3 @@ | ||
var differences = diff(lhs, rhs); | ||
var differences = diff(lhs, rhs); | ||
``` | ||
@@ -94,3 +115,3 @@ The code snippet above would result in the following structure describing the differences: | ||
* `index` - when kind === 'A', indicates the array index where the change occurred | ||
* `item` - when kind === 'A', contains a nested change record indicating the change that occurred at the array index | ||
* `item` - when kind === 'A', contains a nested change record indicating the change that occurred at the array index | ||
@@ -132,8 +153,9 @@ Change records are generated for all structural differences between `origin` and `comparand`. The methods only consider an object's own properties and array elements; those inherited from an object's prototype chain are not considered. | ||
if (d.path.length !== 1 || d.path.join('.') !== 'name') { | ||
applyChange(lhs, rhs, d); | ||
applyChange(lhs, rhs, d); | ||
} | ||
}); | ||
}); | ||
``` | ||
## API Documentation | ||
A standard import of `var diff = require('deep-diff')` is assumed in all of the code examples. The import results in an object having the following public properties: | ||
@@ -140,0 +162,0 @@ |
@@ -1,4 +0,8 @@ | ||
var deep = require('..') | ||
, expect = require('expect.js') | ||
, util = require('util') | ||
if (typeof require === 'function') { | ||
var expect = require('expect.js'), | ||
DeepDiff = require('..') | ||
; | ||
} | ||
var deep = DeepDiff | ||
, executingInBrowser = 'undefined' !== typeof window | ||
; | ||
@@ -95,3 +99,73 @@ | ||
describe('When executing in a browser (otherwise these tests are benign)', function() { | ||
it('#isConflict reports conflict in the global namespace for `DeepDiff`', function() { | ||
// the browser test harness sets up a conflict. | ||
if (executingInBrowser) { | ||
expect(DeepDiff.isConflict).to.be.ok(); | ||
} | ||
}); | ||
it('#noConflict restores prior definition for the global `DeepDiff`', function() { | ||
// the browser test harness sets up a conflict. | ||
if (executingInBrowser) { | ||
expect(DeepDiff.isConflict).to.be.ok(); | ||
var another = DeepDiff.noConflict(); | ||
expect(another).to.be(deep); | ||
expect(DeepDiff).to.be(DeepDiffConflict); | ||
} | ||
}); | ||
}); | ||
describe('A target that has nested values', function() { | ||
var nestedOne = { noChange: 'same', levelOne: { levelTwo: 'value' } }; | ||
var nestedTwo = { noChange: 'same', levelOne: { levelTwo: 'another value' } }; | ||
it('shows no differences when compared to itself', function() { | ||
expect(deep.diff(nestedOne, nestedOne)).to.be.an('undefined'); | ||
}); | ||
it('shows the property as removed when compared to an empty object', function() { | ||
var diff = deep(nestedOne, empty); | ||
expect(diff).to.be.ok(); | ||
expect(diff.length).to.be(2); | ||
expect(diff[0]).to.have.property('kind'); | ||
expect(diff[0].kind).to.be('D'); | ||
expect(diff[1]).to.have.property('kind'); | ||
expect(diff[1].kind).to.be('D'); | ||
}); | ||
it('shows the property is changed when compared to an object that has value', function() { | ||
var diff = deep.diff(nestedOne, nestedTwo); | ||
expect(diff).to.be.ok(); | ||
expect(diff.length).to.be(1); | ||
expect(diff[0]).to.have.property('kind'); | ||
expect(diff[0].kind).to.be('E'); | ||
}); | ||
it('shows the property as added when compared to an empty object on left', function() { | ||
var diff = deep.diff(empty, nestedOne); | ||
expect(diff).to.be.ok(); | ||
expect(diff.length).to.be(2); | ||
expect(diff[0]).to.have.property('kind'); | ||
expect(diff[0].kind).to.be('N'); | ||
}); | ||
describe('when diff is applied to a different empty object', function () { | ||
var diff = deep.diff(nestedOne, nestedTwo); | ||
var result = {}; | ||
it('has result with nested values', function() { | ||
deep.applyChange(result, nestedTwo, diff[0]); | ||
expect(result.levelOne).to.be.ok(); | ||
expect(result.levelOne).to.be.an('object'); | ||
expect(result.levelOne.levelTwo).to.be.ok(); | ||
expect(result.levelOne.levelTwo).to.eql('another value'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
475867
12
3002
165