mirror of
https://github.com/flynx/diff.js.git
synced 2025-10-28 10:30:09 +00:00
334 lines
7.8 KiB
JavaScript
334 lines
7.8 KiB
JavaScript
/**********************************************************************
|
|
*
|
|
*
|
|
*
|
|
**********************************************/ /* c8 ignore next 2 */
|
|
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
|
(function(require){ var module={} // make module AMD/node compatible...
|
|
/*********************************************************************/
|
|
|
|
var types = require('ig-types')
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
// XXX need to deal with functions...
|
|
var HANDLERS =
|
|
module.HANDLERS = {
|
|
/*/ XXX
|
|
// XXX need option threading...
|
|
// XXX need to be able to stop processing handlers...
|
|
// for example when handling 'text' we do not need to also call
|
|
// 'value' too...
|
|
// this can be done via:
|
|
// - a-la evt.preventDefault()
|
|
// - handler.final
|
|
// - callback...
|
|
example: {
|
|
match: function(obj){
|
|
return true },
|
|
handle: function(obj){
|
|
...
|
|
return [key, value, next] },
|
|
},
|
|
//*/
|
|
|
|
null: {
|
|
final: true,
|
|
match: function(obj){
|
|
return obj === null },
|
|
handle: function(obj){
|
|
return [[], obj] },
|
|
},
|
|
|
|
value: {
|
|
match: function(obj){
|
|
return typeof(obj) != 'object' },
|
|
handle: function(obj){
|
|
return [[], obj] },
|
|
},
|
|
|
|
object: {
|
|
match: function(obj){
|
|
return typeof(obj) == 'object' },
|
|
handle: function(obj){
|
|
return [[], {
|
|
// XXX revise...
|
|
type: obj.constructor.name,
|
|
// XXX
|
|
}] },
|
|
},
|
|
|
|
// XXX need to optionally treat special attributes...
|
|
// .__proto__
|
|
specialKeys: {
|
|
//match: function(obj){
|
|
// return typeof(obj) == 'object' },
|
|
handle: function(obj){
|
|
// XXX
|
|
},
|
|
},
|
|
|
|
// XXX these still intersect with attrs...
|
|
// ...need a destinct way to encapsulate these to destinguish
|
|
// the data from attrs...
|
|
// this is simple when nesting, i.e. just add the entries to
|
|
// .entries, attributes to .attrs and done, but in a flat format
|
|
// this is not obvious -- i.e. how do we destinguish attr 'x'
|
|
// from map key 'x'???
|
|
setEntries: {
|
|
match: function(obj){
|
|
return obj instanceof Set },
|
|
// NOTE: we are indexing sets...
|
|
handle: function(obj){
|
|
return [ obj.values()
|
|
.map(function(v, i){
|
|
return [[i], v] })
|
|
.toArray() ] },
|
|
},
|
|
mapEntries: {
|
|
// XXX should this be more generic and just check for .entries(..) ???
|
|
match: function(obj){
|
|
return obj instanceof Map },
|
|
handle: function(obj){
|
|
return [ obj.entries()
|
|
.map(function([k, v], i){
|
|
return [
|
|
// XXX not sure how to format these...
|
|
[[i +':key'], k],
|
|
[[i], v],
|
|
] })
|
|
.flat()
|
|
.toArray() ] },
|
|
},
|
|
|
|
// XXX do we need to treat array keys as a special case???
|
|
// ...the best approach could be to simply:
|
|
// - prioretize handlers -- already done
|
|
// - skip repeating keys
|
|
// ...this could be done on the root handler level...
|
|
// XXX need to optionally handle props...
|
|
keys: {
|
|
match: function(obj){
|
|
return typeof(obj) == 'object' },
|
|
handle: function(obj){
|
|
return [ Object.entries(obj)
|
|
.map(function([k, v]){
|
|
return [[k], v] }), ] },
|
|
},
|
|
}
|
|
|
|
|
|
var getType =
|
|
module.getType =
|
|
function*(obj){
|
|
// XXX
|
|
}
|
|
|
|
|
|
// XXX use STOP...
|
|
var getHandlers =
|
|
module.getHandlers =
|
|
function(obj, handlers=module.HANDLERS){
|
|
return [...Object.entries(handlers)
|
|
.iter()
|
|
.filter(function([k, v]){
|
|
if(v.final
|
|
&& v.match
|
|
&& v.match(obj)){
|
|
throw types.STOP(true) }
|
|
return v.match
|
|
&& v.match(obj) })
|
|
.map(function([k, v]){
|
|
return v })] }
|
|
|
|
|
|
|
|
// Format:
|
|
// [
|
|
// [<path>, {type: <name>}],
|
|
//
|
|
// [<path>, ['LINK', <path>]],
|
|
//
|
|
// [<path>, <value>],
|
|
// ]
|
|
//
|
|
// XXX need a way to index the path...
|
|
// ...and to filter paths by pattern...
|
|
// XXX need to generate object UIDs for use in paths etc...
|
|
// XXX might be a good idea to include obj in the output to negate the
|
|
// need to get it via the path in client code...
|
|
var handle =
|
|
module.handle =
|
|
function*(obj, path=[], options={}){
|
|
// handle recursive structures...
|
|
// XXX would be nice to index paths to make them unique...
|
|
var seen = options.seen =
|
|
options.seen || new Map()
|
|
if(seen.has(obj)){
|
|
// XXX revise format...
|
|
yield [path, ['LINK', seen.get(obj)]]
|
|
return }
|
|
typeof(obj) == 'object'
|
|
&& seen.set(obj, path)
|
|
|
|
// get compatible handler list...
|
|
var cache = options.cache =
|
|
options.cache || new Map()
|
|
var type = getType(obj)
|
|
var handlers =
|
|
(type && cache.get(type))
|
|
|| module.getHandlers(obj, options.handlers || module.HANDLERS)
|
|
type
|
|
&& cache.set(type, handlers)
|
|
|
|
// XXX might be a good idea to move this up (or into options) so as
|
|
// not to define this on each call...
|
|
// ...we'll need to somehow curry in the path which is now passed
|
|
// via a closure...
|
|
var subtree = function*(data){
|
|
// a handler just returned a list of next objects to handle...
|
|
if(data.length == 1){
|
|
var next = data.pop()
|
|
var p = path
|
|
// a normal handler...
|
|
} else {
|
|
var [k, v, next] = data
|
|
var p = path.concat(k)
|
|
yield [p, v] }
|
|
// process queued/next objects...
|
|
yield* (next || [])
|
|
.iter()
|
|
.map(function*([k, v]){
|
|
yield* handle(v, p.concat(k), options) }) }
|
|
|
|
// apply the handlers...
|
|
yield* handlers
|
|
.iter()
|
|
.map(function*(handler){
|
|
yield* handler.handle instanceof types.Generator ?
|
|
handler.handle(obj, path, options)
|
|
.map(subtree)
|
|
: subtree(handler.handle(obj, path, options)) }) }
|
|
|
|
|
|
|
|
// XXX need a better way to serialize the path...
|
|
var serializePathElem = function(p){
|
|
return typeof(p) == 'object' ?
|
|
JSON.stringify(p)
|
|
: p }
|
|
var serializePath = function(p){
|
|
//return '/'+ p.map(JSON.stringify).join('/') }
|
|
return '/'+ p.map(serializePathElem).join('/') }
|
|
var serializePaths =
|
|
module.serializePaths =
|
|
types.generator.iter
|
|
.map(function([p, v]){
|
|
return (
|
|
// XXX revise...
|
|
v instanceof Array && v[0] == 'LINK' ?
|
|
[serializePath(p),
|
|
'LINK', serializePath(v[1])]
|
|
: [serializePath(p), v] ) })
|
|
|
|
|
|
|
|
// XXX make this more generic...
|
|
// ...or move these to the HANDLERS as .build(..)...
|
|
var construct = function(spec){
|
|
return typeof(spec) != 'object' ?
|
|
spec
|
|
: spec.type == 'Object' ?
|
|
{}
|
|
: spec.type == 'Array' ?
|
|
[]
|
|
: spec.type == 'Set' ?
|
|
new Set()
|
|
: spec.type == 'Map' ?
|
|
new Map()
|
|
: undefined }
|
|
var has = function(root, path){
|
|
}
|
|
var get = function(root, path){
|
|
}
|
|
var set = function(root, path, value){
|
|
}
|
|
|
|
var build =
|
|
types.generator.iter
|
|
.reduce(function(root, [path, spec]){
|
|
return path.length == 0 ?
|
|
construct(spec)
|
|
: set(root, path, value)
|
|
}, undefined)
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
// XXX move to test...
|
|
|
|
var o = {
|
|
number: 123,
|
|
string: 'abc',
|
|
|
|
// XXX add a mode to unify these...
|
|
'null': null,
|
|
'undefined': undefined,
|
|
|
|
|
|
empty_array: [],
|
|
array: [1, 2, 3,,,,'N'],
|
|
|
|
// XXX set key is the object itself, is this what we want???
|
|
set: new Set([
|
|
1,
|
|
[],
|
|
{a:1},
|
|
]),
|
|
map: new Map([
|
|
[[9,8,7], 123],
|
|
[321, {x: 123}],
|
|
]),
|
|
|
|
object: {
|
|
x: {},
|
|
},
|
|
|
|
array_with_attrs: Object.assign(
|
|
[1, 2, 3],
|
|
{
|
|
a: 'some value',
|
|
b: 'some other value',
|
|
// will overwrite 2...
|
|
1: 333,
|
|
})
|
|
}
|
|
|
|
// clone...
|
|
// NOTE: JSON does not support:
|
|
// - sparse arrays
|
|
// = sets/maps
|
|
// - loops
|
|
oo = JSON.parse(JSON.stringify(o))
|
|
|
|
// loop...
|
|
// NOTE: we are creating the loop before we pass it to JSON because JSON
|
|
// does not support loops in objects...
|
|
o.object.y = o.object
|
|
|
|
|
|
|
|
console.log([
|
|
...handle(o)
|
|
.chain(serializePaths)])
|
|
|
|
//console.log([...handle(o)])
|
|
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
* vim:set ts=4 sw=4 : */ return module })
|