mirror of
https://github.com/flynx/diff.js.git
synced 2025-10-29 11:00:12 +00:00
refactored the main exec path and made the thing more flexible...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
d83a83b693
commit
6636819d49
370
diff.js
370
diff.js
@ -54,6 +54,10 @@ var DIFF_TYPES = new Set([
|
|||||||
// zip(func, array, array, ...)
|
// zip(func, array, array, ...)
|
||||||
// -> [func(i, [item, item, ...]), ...]
|
// -> [func(i, [item, item, ...]), ...]
|
||||||
//
|
//
|
||||||
|
// XXX still has problems with sparse arrays...
|
||||||
|
// ex:
|
||||||
|
// zip(new Array(5), [])
|
||||||
|
// -> the sparce side will contain undefined instead of being empty...
|
||||||
// XXX revise...
|
// XXX revise...
|
||||||
var zip = function(func, ...arrays){
|
var zip = function(func, ...arrays){
|
||||||
var i = arrays[0] instanceof Array ? 0 : arrays.shift()
|
var i = arrays[0] instanceof Array ? 0 : arrays.shift()
|
||||||
@ -65,7 +69,8 @@ var zip = function(func, ...arrays){
|
|||||||
// NOTE: this is done this way to preserve array sparseness...
|
// NOTE: this is done this way to preserve array sparseness...
|
||||||
var s = arrays
|
var s = arrays
|
||||||
.reduce(function(res, a, j){
|
.reduce(function(res, a, j){
|
||||||
a.length > i
|
//a.length > i
|
||||||
|
i in a
|
||||||
&& (res[j] = a[i])
|
&& (res[j] = a[i])
|
||||||
return res
|
return res
|
||||||
}, new Array(arrays.length))
|
}, new Array(arrays.length))
|
||||||
@ -81,78 +86,6 @@ var zip = function(func, ...arrays){
|
|||||||
: [] }
|
: [] }
|
||||||
|
|
||||||
|
|
||||||
// XXX should we handle properties???
|
|
||||||
// XXX use zip(..)???
|
|
||||||
var _diff_items = function(diff, res, A, B, options, filter){
|
|
||||||
// JSON mode -> ignore attr order...
|
|
||||||
var kA = Object.keys(A)
|
|
||||||
var kB = Object.keys(B)
|
|
||||||
|
|
||||||
if(filter){
|
|
||||||
kA = filter instanceof Array ?
|
|
||||||
filter.slice()
|
|
||||||
: kA.filter(filter)
|
|
||||||
kB = filter instanceof Array ?
|
|
||||||
filter.slice()
|
|
||||||
: kB.filter(filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
var B_index = kB.reduce(function(res, k){
|
|
||||||
res[k] = null
|
|
||||||
return res
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
// items...
|
|
||||||
// XXX use zip(..)...
|
|
||||||
var items = kA
|
|
||||||
// A keys...
|
|
||||||
.map(function(ka){
|
|
||||||
var res = [ka,
|
|
||||||
diff(
|
|
||||||
A[ka],
|
|
||||||
ka in B_index ? B[ka] : EMPTY,
|
|
||||||
options)]
|
|
||||||
// remove seen keys...
|
|
||||||
delete B_index[ka]
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
// keys present only in B...
|
|
||||||
.concat(Object.keys(B_index)
|
|
||||||
.map(function(kb){
|
|
||||||
return [kb,
|
|
||||||
diff(
|
|
||||||
EMPTY,
|
|
||||||
B[kb],
|
|
||||||
options)]}))
|
|
||||||
// cleanup...
|
|
||||||
.filter(function(e){
|
|
||||||
return e[1] !== null })
|
|
||||||
items.length > 0
|
|
||||||
&& (res.items = (res.items || []).concat(items))
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
var _diff_item_order = function(diff, res, A, B, options, filter){
|
|
||||||
var kA = Object.keys(A)
|
|
||||||
var kB = Object.keys(B)
|
|
||||||
|
|
||||||
if(filter){
|
|
||||||
kA = filter instanceof Array ?
|
|
||||||
filter.slice()
|
|
||||||
: kA.filter(filter)
|
|
||||||
kB = filter instanceof Array ?
|
|
||||||
filter.slice()
|
|
||||||
: kB.filter(filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
var item_order = diff(kA, kB, {mode: 'JSON'})
|
|
||||||
item_order != null
|
|
||||||
&& (res.item_order = item_order)
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// get common chuncs (LCS)...
|
// get common chuncs (LCS)...
|
||||||
// XXX handle sparse arrays correctly...
|
// XXX handle sparse arrays correctly...
|
||||||
// ...now empty slots get filled with undefined...
|
// ...now empty slots get filled with undefined...
|
||||||
@ -270,6 +203,89 @@ var getDiffSections = function(A, B, cmp, min_chunk){
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
|
var partHandlers = {
|
||||||
|
// XXX might be good to consider item ordering
|
||||||
|
// ...i.e. how an item's indes changed
|
||||||
|
items: function(diff, A, B, options){
|
||||||
|
return getDiffSections(A, B, options.cmp)
|
||||||
|
.map(function(gap){
|
||||||
|
var i = gap[0][0]
|
||||||
|
var j = gap[1][0]
|
||||||
|
|
||||||
|
return zip(
|
||||||
|
function(n, elems){
|
||||||
|
return [
|
||||||
|
i+n,
|
||||||
|
j+n,
|
||||||
|
diff(
|
||||||
|
0 in elems ? elems[0] : NONE,
|
||||||
|
1 in elems ? elems[1] : NONE,
|
||||||
|
options),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
gap[0][1],
|
||||||
|
gap[1][1])
|
||||||
|
.filter(function(e){ return e[2] != null})
|
||||||
|
})
|
||||||
|
.reduce(function(res, e){
|
||||||
|
return res.concat(e) }, [])
|
||||||
|
},
|
||||||
|
attributes: function(diff, A, B, options, filter){
|
||||||
|
// JSON mode -> ignore attr order...
|
||||||
|
var kA = Object.keys(A)
|
||||||
|
var kB = Object.keys(B)
|
||||||
|
|
||||||
|
if(filter){
|
||||||
|
kA = filter instanceof Array ?
|
||||||
|
filter.slice()
|
||||||
|
: kA.filter(filter)
|
||||||
|
kB = filter instanceof Array ?
|
||||||
|
filter.slice()
|
||||||
|
: kB.filter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
var B_index = kB.reduce(function(res, k){
|
||||||
|
res[k] = null
|
||||||
|
return res
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
// items...
|
||||||
|
// XXX use zip(..)...
|
||||||
|
var items = kA
|
||||||
|
// A keys...
|
||||||
|
.map(function(ka){
|
||||||
|
var res = [ka,
|
||||||
|
diff(
|
||||||
|
A[ka],
|
||||||
|
ka in B_index ? B[ka] : EMPTY,
|
||||||
|
options)]
|
||||||
|
// remove seen keys...
|
||||||
|
delete B_index[ka]
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
// keys present only in B...
|
||||||
|
.concat(Object.keys(B_index)
|
||||||
|
.map(function(kb){
|
||||||
|
return [kb,
|
||||||
|
diff(
|
||||||
|
EMPTY,
|
||||||
|
B[kb],
|
||||||
|
options)]}))
|
||||||
|
// cleanup...
|
||||||
|
.filter(function(e){
|
||||||
|
return e[1] !== null })
|
||||||
|
return items
|
||||||
|
},
|
||||||
|
// XXX
|
||||||
|
order: function(diff, A, B, options){
|
||||||
|
// XXX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
//
|
//
|
||||||
// Format:
|
// Format:
|
||||||
@ -320,32 +336,91 @@ var getDiffSections = function(A, B, cmp, min_chunk){
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
var Types = new Map([
|
||||||
|
['Basic',
|
||||||
|
function(diff, A, B, options){
|
||||||
|
this.A = A
|
||||||
|
this.B = B
|
||||||
|
}],
|
||||||
|
[Object,
|
||||||
|
function(diff, A, B, options){
|
||||||
|
this.items = (this.items || [])
|
||||||
|
.concat(partHandlers.attributes(diff, A, B, options))
|
||||||
|
|
||||||
|
// XXX optional stuff:
|
||||||
|
// - attr ordering...
|
||||||
|
// - prototypes
|
||||||
|
}],
|
||||||
|
[Array,
|
||||||
|
function(diff, A, B, options){
|
||||||
|
this.length = A.length != B.length ? [A.length, B.length] : []
|
||||||
|
this.items = partHandlers.items(diff, A, B, options)
|
||||||
|
}],
|
||||||
|
|
||||||
|
/*/ XXX other JS types...
|
||||||
|
[Map,
|
||||||
|
function(diff, A, B, options){
|
||||||
|
// XXX make the set and map types compatible...
|
||||||
|
// XXX diff [...A.entries()] and [...B.entries()]
|
||||||
|
// ...might be a good idea to sort them too
|
||||||
|
}],
|
||||||
|
[Set, Map],
|
||||||
|
//*/
|
||||||
|
])
|
||||||
|
Types.handle = function(type, obj, ...args){
|
||||||
|
// set .type
|
||||||
|
obj.type = type.name ? type.name : type
|
||||||
|
|
||||||
|
// get the handler while handling aliases...
|
||||||
|
var handler = type
|
||||||
|
do {
|
||||||
|
var handler = this.get(handler)
|
||||||
|
// unhandled type...
|
||||||
|
if(handler == null){
|
||||||
|
throw new TypeError('Diff: can\'t handle: ' + type)
|
||||||
|
}
|
||||||
|
} while(!(handler instanceof Function))
|
||||||
|
|
||||||
|
// call the handler...
|
||||||
|
handler.call(obj, ...args)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// NOTE: this will include direct links to items.
|
// NOTE: this will include direct links to items.
|
||||||
|
// XXX do we need to differentiate things like: new Number(123) vs. 123???
|
||||||
// XXX check seen -- avoid recursion...
|
// XXX check seen -- avoid recursion...
|
||||||
// XXX support Map(..) and other new-style types...
|
// XXX support Map(..) and other new-style types...
|
||||||
var _diff =
|
var _diff =
|
||||||
function(A, B, options, cache){
|
function(A, B, options, cache){
|
||||||
options = options || {}
|
// XXX might be a god idea to mix in default options (different
|
||||||
|
// defaults per mode)...
|
||||||
|
options = options ? Object.create(options) : {}
|
||||||
|
options.cmp = options.cmp || function(a, b){
|
||||||
|
return a === b
|
||||||
|
|| a == b
|
||||||
|
|| (diff(a, b) == null) }
|
||||||
|
// XXX update this depending on mode...
|
||||||
|
options.asObject = options.asObject || []
|
||||||
|
|
||||||
|
|
||||||
// same object...
|
// same object...
|
||||||
// XXX this will miss things like:
|
// XXX do we need to differentiate things like: new Number(123) vs. 123???
|
||||||
// new Number(123) vs. 123
|
|
||||||
// ...would need to also include .value (i.e. .valueOf()) and
|
|
||||||
// treat the thing as object...
|
|
||||||
if(A === B || A == B){
|
if(A === B || A == B){
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// basic types...
|
// basic types...
|
||||||
if(typeof(A) != 'object' || typeof(B) != 'object' || DIFF_TYPES.has(A) || DIFF_TYPES.has(B)){
|
if(typeof(A) != 'object' || typeof(B) != 'object'
|
||||||
return {
|
// return diff placeholders as-is...
|
||||||
type: 'Basic',
|
|| DIFF_TYPES.has(A) || DIFF_TYPES.has(B)){
|
||||||
A: A,
|
return Types.handle('Basic', {}, diff, A, B, options)
|
||||||
B: B,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// cache...
|
// cache...
|
||||||
|
// XXX check seen -- avoid recursion...
|
||||||
cache = cache || new Map()
|
cache = cache || new Map()
|
||||||
var diff = cache.diff = cache.diff || function(a, b){
|
var diff = cache.diff = cache.diff || function(a, b){
|
||||||
var l2 = cache.get(a) || new Map()
|
var l2 = cache.get(a) || new Map()
|
||||||
@ -353,93 +428,59 @@ function(A, B, options, cache){
|
|||||||
cache.set(a, l2.set(b, d))
|
cache.set(a, l2.set(b, d))
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
var cmp = function(a, b){
|
|
||||||
return a === b
|
|
||||||
|| a == b
|
|
||||||
|| (diff(a, b) == null) }
|
|
||||||
|
|
||||||
// Array...
|
|
||||||
// XXX handle sparse arrays correctly...
|
// find the matching type...
|
||||||
// ...now empty slots get filled with undefined...
|
// NOTE: if A and B types mismatch we treat them as Object...
|
||||||
// XXX check seen -- avoid recursion...
|
// XXX this may have issues with key ordering, for example if Object
|
||||||
if(A instanceof Array && B instanceof Array){
|
// is not last it will match any set of items...
|
||||||
var res = {
|
var type = Object
|
||||||
type: 'Array',
|
for(var t of Types.keys()){
|
||||||
length: [A.length, B.length],
|
// skip non-conctructor stuff...
|
||||||
|
if(t === Object
|
||||||
|
// leave pure objects for last...
|
||||||
|
|| !(t instanceof Function)){
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// diff the gaps...
|
// full hit -- type match...
|
||||||
// XXX might be good to consider item ordering...
|
if(A instanceof t && B instanceof t){
|
||||||
res.items = getDiffSections(A, B, cmp)
|
type = t
|
||||||
.map(function(gap){
|
break
|
||||||
var i = gap[0][0]
|
}
|
||||||
var j = gap[1][0]
|
// partial hit -- type mismatch...
|
||||||
|
if(A instanceof t || B instanceof t){
|
||||||
return zip(
|
type = 'Basic'
|
||||||
function(n, elems){
|
break
|
||||||
return [
|
}
|
||||||
i+n,
|
}
|
||||||
j+n,
|
// handle type...
|
||||||
diff(
|
var res = Types.handle(type, {}, diff, A, B, options)
|
||||||
0 in elems ? elems[0] : NONE,
|
// handle things we treat as objects (skipping object itself)...
|
||||||
1 in elems ? elems[1] : NONE),
|
if(type !== Object && type != 'Basic'
|
||||||
]
|
&& (options.asObject == 'all'
|
||||||
},
|
|| options.asObject.indexOf(type) >= 0
|
||||||
gap[0][1],
|
|| (type.name && options.asObject.indexOf(type.name) >= 0))){
|
||||||
gap[1][1])
|
Types.handle(Object, res, diff, A, B, options)
|
||||||
})
|
|
||||||
.reduce(function(res, e){
|
|
||||||
return res.concat(e) }, [])
|
|
||||||
|
|
||||||
/* XXX
|
|
||||||
// attributes...
|
|
||||||
// XXX make this more configurable... (order needs to be optional in JSON)
|
|
||||||
options.mode != 'JSON'
|
|
||||||
&& _diff_items(diff, res, A, B, options,
|
|
||||||
function(e){ return !(e == 0 || !!(e*1)) })
|
|
||||||
// attributes order...
|
|
||||||
&& _diff_item_order(diff, res, A, B, options,
|
|
||||||
function(e){ return !(e == 0 || !!(e*1)) })
|
|
||||||
//*/
|
|
||||||
|
|
||||||
return (res.items || []).length > 0 ? res : null
|
|
||||||
|
|
||||||
// Object...
|
|
||||||
// NOTE: this will handle ONLY own keys...
|
|
||||||
// XXX check seen -- avoid recursion...
|
|
||||||
// XXX handle prototyping... (???)
|
|
||||||
} else {
|
|
||||||
var res = {
|
|
||||||
type: 'Object',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_diff_items(diff, res, A, B, options)
|
// cleanup -- remove items containing empty arrays...
|
||||||
|
Object.keys(res)
|
||||||
|
.filter(function(k){
|
||||||
|
return res[k] instanceof Array && res[k].length == 0 })
|
||||||
|
.map(function(k){
|
||||||
|
delete res[k] })
|
||||||
|
|
||||||
/* XXX
|
// return only non-empty diff states...
|
||||||
// XXX this should be applicable to JSON too...
|
return Object.keys(res).length == 1 ?
|
||||||
options.mode != 'JSON'
|
|
||||||
&& _diff_item_order(diff, res, A, B, options)
|
|
||||||
|
|
||||||
// .constructor...
|
|
||||||
if(options.mode != 'JSON'){
|
|
||||||
A.constructor !== B.constructor
|
|
||||||
&& (res.constructors = [A.constructor, B.constructor])
|
|
||||||
|
|
||||||
// XXX should we diff constructors???
|
|
||||||
|
|
||||||
// XXX .__proto___ (???)
|
|
||||||
}
|
|
||||||
//*/
|
|
||||||
|
|
||||||
return ((res.item_order || []).length
|
|
||||||
+ (res.items || []).length) == 0 ?
|
|
||||||
null
|
null
|
||||||
: res
|
: res
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// XXX need to track order very carefully here... (???)
|
// XXX need to track order very carefully here... (???)
|
||||||
|
// XXX should this follow the same extensible structure as _diff???
|
||||||
|
// ...i.e. type handlers etc.
|
||||||
var flatten =
|
var flatten =
|
||||||
function(diff, res, path){
|
function(diff, res, path){
|
||||||
res = res || []
|
res = res || []
|
||||||
@ -459,7 +500,14 @@ function(diff, res, path){
|
|||||||
|
|
||||||
// Array...
|
// Array...
|
||||||
} else if(diff.type == 'Array'){
|
} else if(diff.type == 'Array'){
|
||||||
diff.items
|
if(diff.length != null){
|
||||||
|
res.push({
|
||||||
|
path: path.concat('length'),
|
||||||
|
A: diff.length[0],
|
||||||
|
B: diff.length[1],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
;(diff.items || [])
|
||||||
.forEach(function(e){
|
.forEach(function(e){
|
||||||
var i = e[0] == e[1] ?
|
var i = e[0] == e[1] ?
|
||||||
e[0]
|
e[0]
|
||||||
@ -472,7 +520,7 @@ function(diff, res, path){
|
|||||||
|
|
||||||
// Object...
|
// Object...
|
||||||
} else if(diff.type == 'Object'){
|
} else if(diff.type == 'Object'){
|
||||||
diff.items
|
;(diff.items || [])
|
||||||
.forEach(function(e){
|
.forEach(function(e){
|
||||||
var i = e[0]
|
var i = e[0]
|
||||||
var v = e[1]
|
var v = e[1]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user