mirror of
https://github.com/flynx/diff.js.git
synced 2025-10-28 18:40:09 +00:00
experimenting with walk(..), need to re-think/re-learn the approach...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
2549003cf1
commit
db363aa47c
347
diff.js
347
diff.js
@ -565,7 +565,7 @@ module.ARRAY =
|
||||
|
||||
// shorthand...
|
||||
// NOTE: yes, ARRAY does not even contain the letter "L" but this is
|
||||
// tradition ;)
|
||||
// tradition ...and it starts off the work [L]ist ;)
|
||||
var L = module.L = ARRAY
|
||||
|
||||
|
||||
@ -1494,6 +1494,86 @@ module.Types = {
|
||||
: res
|
||||
},
|
||||
|
||||
// XXX can we split out the diff walker and simply reuse it for:
|
||||
// .diff(..), .cmp(..), ...
|
||||
// XXX this will produce a flat result out of the box...
|
||||
// XXX this eliminates the need for .flatten(..)
|
||||
// XXX do we need context???
|
||||
_diff: function(A, B, options, context){
|
||||
options = options || {}
|
||||
var that = this
|
||||
|
||||
// basic compare...
|
||||
// XXX do we need to differentiate things like: new Number(123) vs. 123???
|
||||
// XXX do we need to maintain context here???
|
||||
var cmp = function(a, b){
|
||||
// NOTE: we can't use a == b directly because of things like
|
||||
// [2] == 2 -> true...
|
||||
return a === b
|
||||
// basic patters...
|
||||
|| a === that.ANY
|
||||
|| b === that.ANY
|
||||
// logic patterns...
|
||||
|| (a instanceof LogicType
|
||||
&& a.cmp(b))
|
||||
|| (b instanceof LogicType
|
||||
&& b.cmp(a)) }
|
||||
|
||||
options.cmp = cmp
|
||||
|
||||
return walk(
|
||||
function(diff, node, next, stop){
|
||||
var path = node[0]
|
||||
var A = node[1]
|
||||
var B = node[2]
|
||||
|
||||
var cache = this.cache = this.cache || new Map()
|
||||
var cache_l2 = cache.get(A) || new Map()
|
||||
|
||||
// uncached compare...
|
||||
// NOTE: if we already matched A and B they are already
|
||||
// in cache and we do not need to push anything...
|
||||
if(!cache_l2.has(B)){
|
||||
// we have a match -> no changes, just cache...
|
||||
if(cmp(A, B)){
|
||||
cache.set(A, cache_l2.set(B, false))
|
||||
return
|
||||
}
|
||||
|
||||
// get the handler...
|
||||
var handler = that.get(
|
||||
(that.DIFF_TYPES.has(A) || that.DIFF_TYPES.has(B)) ?
|
||||
'Basic'
|
||||
: that.detect(A, B, options))
|
||||
// normalize...
|
||||
handler = handler instanceof Function ?
|
||||
handler
|
||||
: handler && handler._handle ?
|
||||
// NOTE: we do not care about the original
|
||||
// context here as we are going to
|
||||
// .call(..) anyway...
|
||||
handler._handle
|
||||
: false
|
||||
// unhandled type...
|
||||
if(!handler){
|
||||
throw new TypeError('Diff: can\'t handle: ' + type)
|
||||
}
|
||||
|
||||
cache.set(A, cache_l2.set(B, true))
|
||||
|
||||
// call the handler...
|
||||
handler.call(that, diff, path, A, B, next, options)
|
||||
|
||||
return diff
|
||||
}
|
||||
},
|
||||
// diff...
|
||||
[],
|
||||
// node format:
|
||||
// [ <path>, <A>, <B> ]
|
||||
[[], A, B])
|
||||
},
|
||||
|
||||
// Deep-compare A and B...
|
||||
//
|
||||
// XXX would be nice to do a fast fail version of this, i.e. fail on
|
||||
@ -1576,75 +1656,6 @@ module.Types = {
|
||||
.reduce(function(res, e){
|
||||
return res[e] }, obj) },
|
||||
|
||||
// XXX make this an extensible walker...
|
||||
// ...ideally passed a func(A, B, obj, ...) where:
|
||||
// A - change.A
|
||||
// B - change.B
|
||||
// obj - object at change.path
|
||||
// func(..) should be able to:
|
||||
// - replace obj with B/A (patch/unpatch)
|
||||
// ...current implementation of .patch(..)
|
||||
// - check obj against B/A (check)
|
||||
// - swap A and B (reverse) ???
|
||||
// - ...
|
||||
// one way to do this is to pass func(..) a handler that it
|
||||
// would call to control the outcome...
|
||||
// ...still needs thought, but this feels right...
|
||||
_walk: function(diff, obj, func, options){
|
||||
var that = this
|
||||
var NONE = diff.placeholders.NONE
|
||||
var EMPTY = diff.placeholders.EMPTY
|
||||
var options = diff.options
|
||||
|
||||
// NOTE: in .walk(..) we always return the root object bing
|
||||
// patched, this way the handlers have control over the
|
||||
// patching process and it's results on all levels...
|
||||
// ...and this is why we can just pop the last item and
|
||||
// return it...
|
||||
// NOTE: this will do odd things for conflicting patches...
|
||||
// a conflict can be for example patching both a.b and
|
||||
// a.b.c etc.
|
||||
return this.postPatch(this
|
||||
// XXX do we need to merge .walk(..) into this??
|
||||
.walk(diff.diff, function(change){
|
||||
// replace the object itself...
|
||||
if(change.path.length == 0){
|
||||
return change.B
|
||||
}
|
||||
|
||||
var parent
|
||||
var parent_key
|
||||
var target = change.path
|
||||
.slice(0, -1)
|
||||
.reduce(function(res, e){
|
||||
parent = res
|
||||
parent_key = e
|
||||
return res[e]
|
||||
}, obj)
|
||||
var key = change.path[change.path.length-1]
|
||||
|
||||
var type = change.type || Object
|
||||
|
||||
// call the actual patch...
|
||||
// XXX the key can be contextual so we either need to pass
|
||||
// down the context (change and what side we are
|
||||
// looking from, A or B) or make the keys context-free
|
||||
// and handle them here...
|
||||
var res = that.typeCall(type, 'get', target, key)
|
||||
|
||||
// replace the parent value...
|
||||
if(parent){
|
||||
parent[parent_key] = res
|
||||
|
||||
} else {
|
||||
obj = res
|
||||
}
|
||||
|
||||
return obj
|
||||
})
|
||||
.pop())
|
||||
},
|
||||
|
||||
|
||||
// Check if diff is applicable to obj...
|
||||
//
|
||||
@ -1858,6 +1869,19 @@ Types.set('Basic', {
|
||||
|
||||
return change
|
||||
},
|
||||
|
||||
_handle: function(diff, path, A, B, next, options){
|
||||
var obj = {
|
||||
path: path,
|
||||
}
|
||||
|
||||
;(!options.keep_none && A === NONE)
|
||||
|| (obj.A = A)
|
||||
;(!options.keep_none && B === NONE)
|
||||
|| (obj.B = B)
|
||||
|
||||
diff.push(obj)
|
||||
},
|
||||
})
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
@ -1966,6 +1990,69 @@ Types.set(Object, {
|
||||
return e[1] !== null })
|
||||
return items
|
||||
},
|
||||
|
||||
|
||||
// XXX EXPERIMENTAL: used by Types._diff(..)
|
||||
//_walk: function(){},
|
||||
// XXX add attribute order support...
|
||||
_handle: function(diff, path, A, B, next, options){
|
||||
this.get(Object)._attributes.call(this,
|
||||
A, B,
|
||||
function(attr){
|
||||
return next('do', diff,
|
||||
[path.concat(attr[0])].concat(attr.slice(1))) },
|
||||
diff,
|
||||
path,
|
||||
options)
|
||||
// XXX add attribute order support...
|
||||
// XXX
|
||||
},
|
||||
_attributes: function(A, B, next, diff, path, options){
|
||||
next = next || function(e){ return e }
|
||||
|
||||
// get the attributes...
|
||||
// special case: we omit array indexes from the attribute list...
|
||||
var kA = Object.keys(A)
|
||||
kA = A instanceof Array ?
|
||||
kA.slice(A.filter(function(){ return true }).length)
|
||||
: kA
|
||||
var kB = Object.keys(B)
|
||||
kB = B instanceof Array ?
|
||||
kB.slice(B.filter(function(){ return true }).length)
|
||||
: kB
|
||||
|
||||
var B_index = kB.reduce(function(res, k){
|
||||
res[k] = null
|
||||
return res
|
||||
}, {})
|
||||
|
||||
// XXX use zip(..)...
|
||||
var items = kA
|
||||
// A keys...
|
||||
.map(function(ka){
|
||||
var res = [
|
||||
ka,
|
||||
A[ka],
|
||||
ka in B_index ? B[ka] : EMPTY,
|
||||
]
|
||||
// remove seen keys...
|
||||
delete B_index[ka]
|
||||
return res
|
||||
})
|
||||
// keys present only in B...
|
||||
.concat(Object.keys(B_index)
|
||||
.map(function(kb){
|
||||
return [
|
||||
kb,
|
||||
EMPTY,
|
||||
B[kb],
|
||||
]}))
|
||||
.map(next)
|
||||
// cleanup...
|
||||
.filter(function(e){
|
||||
return e != null })
|
||||
return items
|
||||
},
|
||||
})
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
@ -2223,6 +2310,126 @@ Types.set(Array, {
|
||||
order: function(diff, A, B, options){
|
||||
// XXX
|
||||
},
|
||||
|
||||
|
||||
// XXX EXPERIMENTAL: used by Types._diff(..)
|
||||
// XXX BUG: with this the results will not match...
|
||||
// a = [1,{}]
|
||||
// b = [{}]
|
||||
// diff.Diff(
|
||||
// da = diff.Types._diff(a, b),
|
||||
// db = diff.Types.flatten(diff.Types.diff(a, b)) ).diff.length == 0 // -> false
|
||||
// ...this looks to be a difference in cmp(..) not using diff(..)
|
||||
_handle: function(diff, path, A, B, next, options){
|
||||
// items...
|
||||
this.get(Array)._items.call(this,
|
||||
A, B,
|
||||
function(item){
|
||||
return next('do', diff,
|
||||
[path.concat(item[0] == item[1] ?
|
||||
item[0]
|
||||
: item.slice(0, 2))]
|
||||
.concat(item.slice(2))) },
|
||||
diff,
|
||||
path,
|
||||
options)
|
||||
// length...
|
||||
if(A.length != B.length){
|
||||
diff.push({
|
||||
path: path.concat(['length']),
|
||||
A: A.length,
|
||||
B: B.length,
|
||||
})
|
||||
}
|
||||
},
|
||||
_items: function(A, B, next, diff, path, options){
|
||||
next = next || function(e){ return e }
|
||||
diff = diff || []
|
||||
path = path || []
|
||||
|
||||
var NONE = this.NONE
|
||||
var EMPTY = this.EMPTY
|
||||
|
||||
var sections = getDiffSections(A, B, options.cmp)
|
||||
|
||||
// special case: last section set consists of sparse/empty arrays...
|
||||
var last = sections[sections.length-1]
|
||||
last
|
||||
&& last[0][1]
|
||||
.concat(last[1][1])
|
||||
.filter(function(e){ return e }).length == 0
|
||||
&& sections.pop()
|
||||
|
||||
return sections
|
||||
.map(function(gap){
|
||||
var i = gap[0][0]
|
||||
var j = gap[1][0]
|
||||
var a = gap[0][1]
|
||||
var b = gap[1][1]
|
||||
|
||||
// split into two: a common-length section and tails of
|
||||
// 0 and l lengths...
|
||||
var l = Math.min(a.length, b.length)
|
||||
var ta = a.slice(l)
|
||||
var tb = b.slice(l)
|
||||
// tail sections...
|
||||
var tail = {}
|
||||
ta.filter(() => true).length > 0
|
||||
&& (tail.A = ta)
|
||||
tb.filter(() => true).length > 0
|
||||
&& (tail.B = tb)
|
||||
|
||||
a = a.slice(0, l)
|
||||
b = b.slice(0, l)
|
||||
|
||||
return zip(
|
||||
function(n, elems){
|
||||
return next([
|
||||
// indexes...
|
||||
// if a slot exists it gets an index,
|
||||
// otherwise null...
|
||||
(0 in elems || n < a.length) ?
|
||||
i+n
|
||||
: null,
|
||||
(1 in elems || n < b.length) ?
|
||||
j+n
|
||||
: null,
|
||||
// A...
|
||||
// use value, EMPTY or NONE...
|
||||
0 in elems ?
|
||||
elems[0]
|
||||
: n < a.length ?
|
||||
EMPTY
|
||||
: NONE,
|
||||
// B...
|
||||
1 in elems ?
|
||||
elems[1]
|
||||
: n < b.length ?
|
||||
EMPTY
|
||||
: NONE,
|
||||
]) },
|
||||
a, b)
|
||||
// clear matching stuff...
|
||||
.filter(function(e){
|
||||
return e != null })
|
||||
// splice array sub-sections...
|
||||
.concat((ta.length + tb.length > 0 ?
|
||||
[Object.assign({},
|
||||
{
|
||||
path: path.concat([[
|
||||
[i+l, ta.length],
|
||||
[j+l, tb.length],
|
||||
]]),
|
||||
},
|
||||
tail)]
|
||||
: [])
|
||||
.map(function(e){
|
||||
return (diff.push(e), e) }))
|
||||
})
|
||||
// XXX do we need this???
|
||||
.reduce(function(res, e){
|
||||
return res.concat(e) }, [])
|
||||
},
|
||||
})
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user