mirror of
				https://github.com/flynx/diff.js.git
				synced 2025-10-31 03:50:13 +00:00 
			
		
		
		
	finalized the patch architecture, still need to revize the format + some refactoring...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
		
							parent
							
								
									1fd00a2f72
								
							
						
					
					
						commit
						d1ef8cc95a
					
				
							
								
								
									
										276
									
								
								diff.js
									
									
									
									
									
								
							
							
						
						
									
										276
									
								
								diff.js
									
									
									
									
									
								
							| @ -6,6 +6,14 @@ | |||||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||||
| (function(require){ var module={} // make module AMD/node compatible...
 | (function(require){ var module={} // make module AMD/node compatible...
 | ||||||
| /*********************************************************************/ | /*********************************************************************/ | ||||||
|  | 
 | ||||||
|  | var ANY = {type: 'ANY_PLACEHOLDER'} | ||||||
|  | var NONE = {type: 'NONE_PLACEHOLDER'} | ||||||
|  | var EMPTY = {type: 'EMPTY_PLACEHOLDER'} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | //---------------------------------------------------------------------
 | ||||||
| // Helpers...
 | // Helpers...
 | ||||||
| 
 | 
 | ||||||
| // 	zip(array, array, ...)
 | // 	zip(array, array, ...)
 | ||||||
| @ -212,6 +220,9 @@ var proxy = function(path, func){ | |||||||
| // 			
 | // 			
 | ||||||
| // 			A: <value>,
 | // 			A: <value>,
 | ||||||
| // 			B: <value>,
 | // 			B: <value>,
 | ||||||
|  | //
 | ||||||
|  | // 			// optional payload data...
 | ||||||
|  | // 			...
 | ||||||
| // 		}
 | // 		}
 | ||||||
| // 			
 | // 			
 | ||||||
| // 		// A and B are arrays...
 | // 		// A and B are arrays...
 | ||||||
| @ -288,6 +299,9 @@ var proxy = function(path, func){ | |||||||
| //			//						or null then it means that the item
 | //			//						or null then it means that the item
 | ||||||
| //			//						does not exist in the corresponding
 | //			//						does not exist in the corresponding
 | ||||||
| //			//						array...
 | //			//						array...
 | ||||||
|  | //			//				  NOTE: if both of the array items are arrays
 | ||||||
|  | //			//						it means that we are splicing array 
 | ||||||
|  | //			//						sections instead of array elements...
 | ||||||
| // 			path: [<key>, ...],
 | // 			path: [<key>, ...],
 | ||||||
| //
 | //
 | ||||||
| // 			// values in A and B...
 | // 			// values in A and B...
 | ||||||
| @ -300,6 +314,10 @@ var proxy = function(path, func){ | |||||||
| // 			//	EMPTY		- the slot exists but it is empty (set/delete)
 | // 			//	EMPTY		- the slot exists but it is empty (set/delete)
 | ||||||
| // 			A: <value> | EMPTY | NONE,
 | // 			A: <value> | EMPTY | NONE,
 | ||||||
| // 			B: <value> | EMPTY | NONE,
 | // 			B: <value> | EMPTY | NONE,
 | ||||||
|  | //
 | ||||||
|  | // 			// used if we are splicing array sections to indicate section
 | ||||||
|  | // 			// lengths, useful when splicing sparse sections...
 | ||||||
|  | // 			length: [a, b],
 | ||||||
| // 		},
 | // 		},
 | ||||||
| // 		...
 | // 		...
 | ||||||
| // 	]
 | // 	]
 | ||||||
| @ -311,9 +329,12 @@ var proxy = function(path, func){ | |||||||
| // 		like the type information which is not needed for patching but 
 | // 		like the type information which is not needed for patching but 
 | ||||||
| // 		may be useful for a more thorough compatibility check.
 | // 		may be useful for a more thorough compatibility check.
 | ||||||
| var Types = { | var Types = { | ||||||
|  | 	__cache: null, | ||||||
|  | 
 | ||||||
| 	// Object-level utilities...
 | 	// Object-level utilities...
 | ||||||
| 	clone: function(){ | 	clone: function(){ | ||||||
| 		var res = Object.create(this) | 		var res = Object.create(this) | ||||||
|  | 		res.__cache = null | ||||||
| 		res.handlers = new Map(this.handlers.entries()) | 		res.handlers = new Map(this.handlers.entries()) | ||||||
| 		return res | 		return res | ||||||
| 	}, | 	}, | ||||||
| @ -570,13 +591,13 @@ var Types = { | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// builtin types...
 | 		// builtin types...
 | ||||||
| 		if(DIFF_TYPES.has(A) || DIFF_TYPES.has(B)){ | 		if(this.DIFF_TYPES.has(A) || this.DIFF_TYPES.has(B)){ | ||||||
| 			return this.handle('Basic', {}, diff, A, B, options) | 			return this.handle('Basic', {}, diff, A, B, options) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 		// cache...
 | 		// cache...
 | ||||||
| 		cache = cache || new Map() | 		cache = this.__cache = cache || this.__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() | ||||||
| 			var d = l2.get(b) || that.diff(a, b, options, cache) | 			var d = l2.get(b) || that.diff(a, b, options, cache) | ||||||
| @ -610,15 +631,44 @@ var Types = { | |||||||
| 			: res | 			: res | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
|  | 	// Deep-compare A and B...
 | ||||||
|  | 	//
 | ||||||
|  | 	cmp: function(A, B, options){ | ||||||
|  | 		return this.diff(A, B, options) == null }, | ||||||
|  | 
 | ||||||
| 	// Patch (update) obj via diff...
 | 	// Patch (update) obj via diff...
 | ||||||
| 	//
 | 	//
 | ||||||
|  | 	// XXX would need to let the type handlers handle themselves a-la .handle(..)
 | ||||||
| 	patch: function(diff, obj, options){ | 	patch: function(diff, obj, options){ | ||||||
| 		// XXX approach:
 | 		var NONE = diff.placeholders.NONE | ||||||
| 		// 		- check
 | 		var EMPTY = diff.placeholders.EMPTY | ||||||
| 		// 		- flatten
 | 		var options = diff.options | ||||||
| 		// 		- run patch
 | 
 | ||||||
| 		// 			- might be a good idea to enable handlers to handle 
 | 		this.walk(diff.diff, function(change){ | ||||||
| 		// 			  their own updates...
 | 			// replace the object itself...
 | ||||||
|  | 			if(change.path.length == 0){ | ||||||
|  | 				return change.B | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			var type = change.type || Object | ||||||
|  | 
 | ||||||
|  | 			var target = change.path | ||||||
|  | 				.slice(0, -1) | ||||||
|  | 				.reduce(function(res, e){ | ||||||
|  | 					return res[e]}, obj) | ||||||
|  | 			var key = change.path[change.path.length-1] | ||||||
|  | 
 | ||||||
|  | 			// XXX this needs to be able to replace the target...
 | ||||||
|  | 			this.getHandler(type).patch.call(this, target, key, change, options) | ||||||
|  | 		}) | ||||||
|  | 		return obj | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	// Reverse diff...
 | ||||||
|  | 	//
 | ||||||
|  | 	// XXX should we do this or reverse patch / undo-patch???
 | ||||||
|  | 	reverse: function(diff){ | ||||||
|  | 		// XXX
 | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	// Check if diff is applicable to obj...
 | 	// Check if diff is applicable to obj...
 | ||||||
| @ -626,6 +676,7 @@ var Types = { | |||||||
| 	check: function(diff, obj, options){ | 	check: function(diff, obj, options){ | ||||||
| 		// XXX
 | 		// XXX
 | ||||||
| 	}, | 	}, | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -715,6 +766,8 @@ var Types = { | |||||||
| // NOTE: a basic type is one that returns a specific non-'object'
 | // NOTE: a basic type is one that returns a specific non-'object'
 | ||||||
| // 		typeof...
 | // 		typeof...
 | ||||||
| // 		i.e. when typeof(x) != 'object'
 | // 		i.e. when typeof(x) != 'object'
 | ||||||
|  | // NOTE: this does not need a .patch(..) method because it is not a 
 | ||||||
|  | // 		container...
 | ||||||
| Types.set('Basic', { | Types.set('Basic', { | ||||||
| 	priority: 50, | 	priority: 50, | ||||||
| 
 | 
 | ||||||
| @ -727,13 +780,10 @@ Types.set('Basic', { | |||||||
| 			|| (obj.B = B) | 			|| (obj.B = B) | ||||||
| 	}, | 	}, | ||||||
| 	walk: function(diff, func, path){ | 	walk: function(diff, func, path){ | ||||||
| 		var change = { | 		var change = Object.assign({ | ||||||
| 			path: path, | 			path: path, | ||||||
| 		} | 		}, diff) | ||||||
| 		'A' in diff  | 		delete change.type | ||||||
| 			&& (change.A = diff.A) |  | ||||||
| 		'B' in diff  |  | ||||||
| 			&& (change.B = diff.B) |  | ||||||
| 		return func(change) | 		return func(change) | ||||||
| 	}, | 	}, | ||||||
| }) | }) | ||||||
| @ -765,6 +815,24 @@ Types.set(Object, { | |||||||
| 				return that.walk(v, func, p) | 				return that.walk(v, func, p) | ||||||
| 			}) | 			}) | ||||||
| 	}, | 	}, | ||||||
|  | 	// XXX add object compatibility checks...
 | ||||||
|  | 	patch: function(obj, key, change){ | ||||||
|  | 		// object attr...
 | ||||||
|  | 		if(typeof(key) == typeof('str')){ | ||||||
|  | 			if(this.cmp(change.B, EMPTY)){ | ||||||
|  | 				delete obj[key] | ||||||
|  | 
 | ||||||
|  | 			} else { | ||||||
|  | 				obj[key] = change.B | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		// array item...
 | ||||||
|  | 		// XXX should this make this decision???
 | ||||||
|  | 		} else { | ||||||
|  | 			return this.getHandler(Array).patch.call(this, obj, key, change) | ||||||
|  | 		} | ||||||
|  | 		return obj | ||||||
|  | 	}, | ||||||
| 
 | 
 | ||||||
| 	// part handlers...
 | 	// part handlers...
 | ||||||
| 	attributes: function(diff, A, B, options, filter){ | 	attributes: function(diff, A, B, options, filter){ | ||||||
| @ -855,6 +923,62 @@ Types.set(Array, { | |||||||
| 				}) | 				}) | ||||||
| 				: []) | 				: []) | ||||||
| 	}, | 	}, | ||||||
|  | 	// XXX add object compatibility checks...
 | ||||||
|  | 	patch: function(obj, key, change){ | ||||||
|  | 		var i = key instanceof Array ? key[0] : key | ||||||
|  | 		var j = key instanceof Array ? key[1] : key | ||||||
|  | 
 | ||||||
|  | 		// sub-array manipulation...
 | ||||||
|  | 		if(i instanceof Array){ | ||||||
|  | 			i = i[0] | ||||||
|  | 			j = j[0] | ||||||
|  | 
 | ||||||
|  | 			// XXX check compatibility...
 | ||||||
|  | 
 | ||||||
|  | 			obj.splice(j,  | ||||||
|  | 				'A' in change ?  | ||||||
|  | 					change.A.length | ||||||
|  | 					: change.length[0],  | ||||||
|  | 				...('B' in change ?  | ||||||
|  | 					change.B | ||||||
|  | 					: new Array(change.length[1]))) | ||||||
|  | 
 | ||||||
|  | 		// item manipulation...
 | ||||||
|  | 		} else { | ||||||
|  | 			if(i == null){ | ||||||
|  | 				// XXX this will mess up the indexing for the rest of
 | ||||||
|  | 				// 		item removals...
 | ||||||
|  | 				obj.splice(j, 0, change.B) | ||||||
|  | 
 | ||||||
|  | 			} else if(j == null){ | ||||||
|  | 				// obj explicitly empty...
 | ||||||
|  | 				if('B' in change && this.cmp(change.B, EMPTY)){ | ||||||
|  | 					delete obj[i] | ||||||
|  | 
 | ||||||
|  | 				// splice out obj...
 | ||||||
|  | 				} else if(!('B' in change) || this.cmp(change.B, NONE)){ | ||||||
|  | 					// NOTE: this does not affect the later elements
 | ||||||
|  | 					// 		indexing as it essentially shifts the 
 | ||||||
|  | 					// 		indexes to their obj state for next 
 | ||||||
|  | 					// 		changes...
 | ||||||
|  | 					obj.splice(i, 1) | ||||||
|  | 
 | ||||||
|  | 				// XXX
 | ||||||
|  | 				} else { | ||||||
|  | 					// XXX
 | ||||||
|  | 					console.log('!!!!!!!!!!') | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 			} else if(i == j){ | ||||||
|  | 				obj[j] = change.B | ||||||
|  | 
 | ||||||
|  | 			} else { | ||||||
|  | 				obj[j] = change.B | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		return obj | ||||||
|  | 	}, | ||||||
| 
 | 
 | ||||||
| 	// part handlers...
 | 	// part handlers...
 | ||||||
| 	items: function(diff, A, B, options){ | 	items: function(diff, A, B, options){ | ||||||
| @ -877,6 +1001,24 @@ Types.set(Array, { | |||||||
| 				var a = gap[0][1] | 				var a = gap[0][1] | ||||||
| 				var b = gap[1][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...
 | ||||||
|  | 				// XXX hack???
 | ||||||
|  | 				// XXX should we use a different type/sub-type???
 | ||||||
|  | 				var tail = { type: 'Basic', } | ||||||
|  | 				ta.filter(() => true).length > 0  | ||||||
|  | 					&& (tail.A = ta) | ||||||
|  | 				tb.filter(() => true).length > 0  | ||||||
|  | 					&& (tail.B = tb) | ||||||
|  | 				tail.length = [ta.length, tb.length] | ||||||
|  | 
 | ||||||
|  | 				a = a.slice(0, l) | ||||||
|  | 				b = b.slice(0, l) | ||||||
|  | 
 | ||||||
| 				return zip( | 				return zip( | ||||||
| 						function(n, elems){ | 						function(n, elems){ | ||||||
| 							return [ | 							return [ | ||||||
| @ -903,8 +1045,17 @@ Types.set(Array, { | |||||||
| 									options),  | 									options),  | ||||||
| 							] },  | 							] },  | ||||||
| 						a, b) | 						a, b) | ||||||
| 					.filter(function(e){  | 					// clear matching stuff...
 | ||||||
|  | 					 .filter(function(e){  | ||||||
| 						return e[2] != null}) | 						return e[2] != null}) | ||||||
|  | 					// splice array sub-sections...
 | ||||||
|  | 					.concat(ta.length + tb.length > 0 ? | ||||||
|  | 						[[ | ||||||
|  | 							[i+l], | ||||||
|  | 							[j+l], | ||||||
|  | 							tail, | ||||||
|  | 						]] | ||||||
|  | 						: []) | ||||||
| 			}) | 			}) | ||||||
| 			.reduce(function(res, e){  | 			.reduce(function(res, e){  | ||||||
| 				return res.concat(e) }, []) | 				return res.concat(e) }, []) | ||||||
| @ -957,6 +1108,7 @@ Types.set('Text', { | |||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	// XXX
 | 	// XXX
 | ||||||
|  | 	// XXX add object compatibility checks...
 | ||||||
| 	patch: function(change, obj){ | 	patch: function(change, obj){ | ||||||
| 		// XXX
 | 		// XXX
 | ||||||
| 		 | 		 | ||||||
| @ -977,7 +1129,7 @@ Types.set('Text', { | |||||||
| var cmp = | var cmp = | ||||||
| module.cmp = | module.cmp = | ||||||
| function(A, B){ | function(A, B){ | ||||||
| 	return Types.diff(A, B) == null ? true : false } | 	return Types.clone().cmp(A, B) } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| // Diff interface function...
 | // Diff interface function...
 | ||||||
| @ -1098,98 +1250,6 @@ function(diff, obj, options, types){ | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| // XXX would need to let the type handlers handle themselves a-la .handle(..)
 |  | ||||||
| // XXX Problems:
 |  | ||||||
| // 		_patch(diff(i = [1,2], [2,1]), i)
 |  | ||||||
| //			-> [2,2]
 |  | ||||||
| var _patch = function(diff, obj){ |  | ||||||
| 	var NONE = diff.placeholders.NONE |  | ||||||
| 	var EMPTY = diff.placeholders.EMPTY |  | ||||||
| 	var options = diff.options |  | ||||||
| 
 |  | ||||||
| 	// XXX also check what is overwritten...
 |  | ||||||
| 	// XXX need to correctly check EMPTY/NONE...
 |  | ||||||
| 	var checkTypeMatch = function(change, target, key){ |  | ||||||
| 		if('A' in change  |  | ||||||
| 				&& !(cmp(change.A, EMPTY) ?  |  | ||||||
| 					!(key in target) |  | ||||||
| 					: cmp(target[key], change.A))){ |  | ||||||
| 			console.warn('Patch: Mismatching values at:', change.path,  |  | ||||||
| 				'expected:', change.A,  |  | ||||||
| 				'got:', target[key]) |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	Types.walk(diff.diff, function(change){ |  | ||||||
| 		// replace the object itself...
 |  | ||||||
| 		if(change.path.length == 0){ |  | ||||||
| 			return change.B |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var type = change.type || 'item' |  | ||||||
| 
 |  | ||||||
| 		var target = change.path |  | ||||||
| 			.slice(0, -1) |  | ||||||
| 			.reduce(function(res, e){ |  | ||||||
| 				return res[e]}, obj) |  | ||||||
| 		var key = change.path[change.path.length-1] |  | ||||||
| 
 |  | ||||||
| 		if(type == 'item'){ |  | ||||||
| 			// object attr...
 |  | ||||||
| 			if(typeof(key) == typeof('str')){ |  | ||||||
| 				if(cmp(change.B, EMPTY)){ |  | ||||||
| 					delete target[key] |  | ||||||
| 
 |  | ||||||
| 				} else { |  | ||||||
| 					checkTypeMatch(change, target, key) |  | ||||||
| 
 |  | ||||||
| 					target[key] = change.B |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 			// array item...
 |  | ||||||
| 			} else { |  | ||||||
| 				var i = key instanceof Array ? key[0] : key |  | ||||||
| 				var j = key instanceof Array ? key[1] : key |  | ||||||
| 
 |  | ||||||
| 				// XXX check A...
 |  | ||||||
| 
 |  | ||||||
| 				if(i == null){ |  | ||||||
| 					target.splice(j, 0, change.B) |  | ||||||
| 
 |  | ||||||
| 				} else if(j == null){ |  | ||||||
| 					// target explicitly empty...
 |  | ||||||
| 					if('B' in change && cmp(change.B, EMPTY)){ |  | ||||||
| 						delete target[i] |  | ||||||
| 
 |  | ||||||
| 					// splice out target...
 |  | ||||||
| 					} else if(!('B' in change) || cmp(change.B, NONE)){ |  | ||||||
| 						target.splice(i, 1) |  | ||||||
| 
 |  | ||||||
| 					// XXX
 |  | ||||||
| 					} else { |  | ||||||
| 						// XXX
 |  | ||||||
| 						console.log('!!!!!!!!!!') |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 				} else if(i == j){ |  | ||||||
| 					target[j] = change.B |  | ||||||
| 
 |  | ||||||
| 				} else { |  | ||||||
| 					target[j] = change.B |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 		// custom types...
 |  | ||||||
| 		} else { |  | ||||||
| 			// XXX revise...
 |  | ||||||
| 			obj = this.getHandler(type).patch.call(this, change, obj) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 	}) |  | ||||||
| 	return obj |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user