Skip to content

Commit 8b60a91

Browse files
committed
v0.0.1
0 parents  commit 8b60a91

10 files changed

+334
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
npm-debug.log
2+
node_modules

.tm_properties

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
excludeInFileChooser = "{$excludeInFileChooser,node_modules}"
2+
excludeInBrowser = "{$excludeInBrowser}"
3+
excludeInFolderSearch = "{$excludeInFolderSearch,node_modules}"

Gruntfile.coffee

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
module.exports = (grunt) ->
3+
grunt.loadNpmTasks 'grunt-mocha-test'
4+
grunt.loadNpmTasks 'grunt-contrib-coffee'
5+
grunt.loadNpmTasks 'grunt-contrib-watch'
6+
grunt.loadNpmTasks 'grunt-codo'
7+
8+
grunt.initConfig
9+
pkg: grunt.file.readJSON("package.json")
10+
11+
coffee:
12+
compile:
13+
files:
14+
'lib/index.js': 'src/index.coffee'
15+
16+
watch:
17+
coffee:
18+
files: ['src/index.coffee'],
19+
tasks: ['coffee']
20+
21+
mochaTest:
22+
test:
23+
options:
24+
reporter: 'spec'
25+
require: [
26+
'coffee-script'
27+
]
28+
src: ['spec/**/*.coffee']
29+
30+
grunt.registerTask 'doc', ['codo']
31+
grunt.registerTask 'test', ['mochaTest']
32+
grunt.registerTask "compile", ["coffee"]
33+
grunt.registerTask "default", ["compile"]

README.md

Whitespace-only changes.

bin/coffee

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
`dirname $0`/../node_modules/coffee-script/bin/coffee $@

bin/grunt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
`dirname $0`/../node_modules/grunt-cli/bin/grunt $@

lib/index.js

+108
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "rus-diff",
3+
"version": "0.0.1",
4+
"description": "MongoDB compatible JSON diff.",
5+
"main": "lib/index.js",
6+
"scripts": {
7+
"test": "bin/grunt test"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git://github.com/mirek/node-rus-diff.git"
12+
},
13+
"keywords": [
14+
"json",
15+
"diff",
16+
"mongo",
17+
"mongodb"
18+
],
19+
"author": "Mirek Rusin http://github.com/mirek",
20+
"license": "MIT",
21+
"bugs": {
22+
"url": "https://github.com/mirek/node-rus-diff/issues"
23+
},
24+
"homepage": "https://github.com/mirek/node-rus-diff",
25+
"devDependencies": {
26+
"grunt-mocha-test": "^0.9.4",
27+
"mocha": "^1.17.1",
28+
"grunt-cli": "^0.1.13",
29+
"grunt": "^0.4.3",
30+
"coffee-script": "^1.7.1",
31+
"grunt-contrib-coffee": "^0.10.1",
32+
"grunt-codo": "^0.1.0",
33+
"grunt-contrib-watch": "^0.5.3"
34+
}
35+
}

spec/spec_index.coffee

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
assert = require 'assert'
2+
{rusDiff} = require '../src'
3+
4+
describe 'rusDiff', ->
5+
it 'should produce no difference', ->
6+
assert.deepEqual {}, rusDiff {}, {}
7+
8+
it 'should produce scoped diff', ->
9+
a =
10+
foo:
11+
bb:
12+
inner:
13+
this_is_a: 1
14+
to_rename: "Hello"
15+
aa: 1
16+
bar: 1
17+
replace_me: 1
18+
19+
b =
20+
foo:
21+
bb:
22+
inner:
23+
this_is_b: 2
24+
cc:
25+
new_val: 2
26+
bar2: 2
27+
zz: 2
28+
renamed: "Hello"
29+
replace_me: 2
30+
31+
r =
32+
$rename:
33+
"my.value.foo.bb.inner.to_rename": "my.value.renamed"
34+
$unset:
35+
"my.value.bar": true
36+
"my.value.foo.aa": true
37+
"my.value.foo.bb.inner.this_is_a": true
38+
$set:
39+
"my.value.bar2": 2
40+
"my.value.foo.bb.inner.this_is_b": 2
41+
"my.value.foo.cc":
42+
new_val: 2
43+
"my.value.replace_me": 2
44+
"my.value.zz": 2
45+
46+
assert.deepEqual r, rusDiff a, b, ['my', 'value']

src/index.coffee

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
2+
# Difference between JSON objects.
3+
#
4+
# @param [Object] a
5+
# @param [Object] b
6+
# @param [Array] stack Optional scope.
7+
# @param [Boolean] rename Internal
8+
# @param [Object] garbage Internal
9+
#
10+
# @return [Object] Difference between b - a JSON objects.
11+
#
12+
rusDiff = (a, b, stack = [], rename = true, garbage = {}) ->
13+
14+
# Sorted lists of keys.
15+
aKeys = Object.keys(a).sort()
16+
bKeys = Object.keys(b).sort()
17+
18+
# Number of keys.
19+
aN = aKeys.length
20+
bN = bKeys.length
21+
22+
# Key indices.
23+
aI = 0
24+
bI = 0
25+
26+
# Result.
27+
rus =
28+
$rename: {}
29+
$unset: {}
30+
$set: {}
31+
32+
# Unset
33+
unsetA = (i) ->
34+
key = (stack.concat aKeys[i]).join('.')
35+
rus.$unset[key] = true
36+
(garbage[ a[aKeys[i]] ] ||= []).push key
37+
38+
setB = (i) ->
39+
key = (stack.concat bKeys[i]).join('.')
40+
rus.$set[key] = b[bKeys[i]]
41+
42+
while (aI < aN) and (bI < bN)
43+
aKey = aKeys[aI]
44+
bKey = bKeys[bI]
45+
46+
if aKey is bKey
47+
aVal = a[aKey]
48+
bVal = b[bKey]
49+
if aVal isnt bVal
50+
if (typeof aVal is 'object') and (typeof bVal is 'object')
51+
for k, v of rusDiff(aVal, bVal, stack.concat([aKey]), false, garbage)
52+
53+
# Merge changes
54+
for k2, v2 of v
55+
rus[k][k2] = v2
56+
else
57+
58+
# At least one is not an Object (hash), b overwrites a.
59+
#
60+
# NOTE: aVal doesn't go to garbage (as a potential rename) because MongoDB 2.4.x doesn't allow $set
61+
# and $rename for the same key paths giving MongoDB error 10150: "exception: Field name duplication
62+
# not allowed with modifiers"
63+
setB bI
64+
++aI
65+
++bI
66+
else
67+
if aKey < bKey
68+
unsetA aI
69+
++aI
70+
else
71+
setB bI
72+
++bI
73+
74+
# Finish remaining a keys if any left.
75+
while aI < aN
76+
unsetA aI++
77+
78+
# Finish remaining b keys if any left.
79+
while bI < bN
80+
setB bI++
81+
82+
if rename
83+
84+
# Diff has been completed, root invocation wants to do the rename, collect from garbage
85+
# whatever we can.
86+
collect = ([k, key] for k, v of rus.$set when garbage[v]? and (key = garbage[v].pop()))
87+
for e in collect
88+
[k, key] = e
89+
rus.$rename[key] = k
90+
delete rus.$unset[key]
91+
delete rus.$set[k]
92+
93+
# Return non-empty modifications only.
94+
for k of rus
95+
if Object.keys(rus[k]).length is 0
96+
delete rus[k]
97+
98+
rus
99+
100+
if module? and module.exports?
101+
module.exports.rusDiff = rusDiff

0 commit comments

Comments
 (0)