diff --git a/diff.js b/diff.js new file mode 100644 index 0000000..b6d3c68 --- /dev/null +++ b/diff.js @@ -0,0 +1,275 @@ +/********************************************************************** +* +* +* +**********************************************************************/ +((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) +(function(require){ var module={} // make module AMD/node compatible... +/*********************************************************************/ + + + + +/*********************************************************************/ +// Diff base format: +// { +// varsion: '1.0', +// options: { +// }, +// +// diff: , +// } +// +// Diff format: +// [ +// ] +// +// +// +/*********************************************************************/ + +var EMPTY = {type: 'EMPTY'} + + +// Helpers... +// XXX should we handle properties??? +var _diff_items = function(diff, A, B, options, filter){ + // JSON mode -> ignore attr order... + var kA = Object.keys(A) + var kB = Object.keys(B) + + if(filter){ + kA = kA.filter(filter) + kB = kB.filter(filter) + } + + var B_index = kB.reduce(function(res, k){ + res[k] = null + return res + }, {}) + + // items... + 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 + && (diff.items = (diff.items || []).concat(items)) + + return diff +} +var _diff_item_order = function(diff, A, B, options, filter){ + var kA = Object.keys(A) + var kB = Object.keys(B) + + if(filter){ + kA = kA.filter(filter) + kB = kB.filter(filter) + } + + var item_order = _diff(kA, kB, {mode: 'JSON'}) + item_order != null + && (diff.item_order = item_order) + + return diff +} + + +// Format: +// - no difference... +// null +// +// - A and/or B is a basic value... +// { +// type: 'Basic', +// +// A: , +// B: , +// } +// +// - A and B are arrays... +// { +// type: 'Array', +// // holds both index and attribute keys (mode-dependant)... +// +// items: [ +// [, ], +// ... +// ], +// // only for non-index keys... +// item_order: , +// } +// +// - A and B are objects... +// { +// type: 'Object', +// +// items: [ +// [, ], +// ... +// ], +// item_order: , +// } +// +// +// NOTE: this will include direct links to items. +// XXX check seen -- avoid recursion... +// XXX revise format... +var _diff = +function(A, B, options){ + options = options || {} + + // same object... + // XXX this will miss things like: + // 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 || (options.mode == 'JSON' && A == B)){ + return null + } + + // basic types... + if(typeof(A) != 'object' || typeof(B) != 'object'){ + return { + type: 'Basic', + //values: [A, B], + A: A, + B: B, + } + } + + // Array... + // XXX check seen -- avoid recursion... + if(A instanceof Array && B instanceof Array){ + var res = { + type: 'Array', + } + + // indexed items... + _diff_items(res, A, B, options, + function(e){ return e == 0 || !!(e*1) }) + + // attributes... + options.mode != 'JSON' + && _diff_items(res, A, B, options, + function(e){ return !(e == 0 || !!(e*1)) }) + // attributes order... + && _diff_item_order(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(res, A, B, options) + + // XXX this should be applicable to JSON too... + options.mode != 'JSON' + && _diff_item_order(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 : res + } +} + + +// XXX need to track order very carefully here... (???) +var flatten = +function(diff, res, path){ + res = res || [] + path = path || [] + + // no difference... + if(diff == null){ + return res + + // Basic... + } else if(diff.type == 'Basic'){ + res.push({ + path: path, + A: diff.A, + B: diff.B, + }) + + // Array... + } else if(diff.type == 'Array'){ + diff.items.forEach(function(e){ + var i = e[0] + var v = e[1] + var p = path.concat([i]) + + flatten(v, res, p) + }) + + // Object... + } else if(diff.type == 'Object'){ + diff.items.forEach(function(e){ + var i = e[0] + var v = e[1] + var p = path.concat([i]) + + flatten(v, res, p) + }) + + // Other... + // XXX revise this... + } else { + throw new TypeError('Unknown diff type: '+ diff.type) + } + + return res +} + + +var diff = +module.diff = +function(A, B, options){ +} + + +var patch = +module.patch = +function(diff, obj){ +} + + + + +/********************************************************************** +* vim:set ts=4 sw=4 : */ return module })