145 lines
3.8 KiB
JavaScript
145 lines
3.8 KiB
JavaScript
var is = require('./is');
|
|
|
|
|
|
var lib = {
|
|
attributes: {
|
|
clone: function (attributes, keepNull) {
|
|
if (!is.object(attributes)) return {};
|
|
return Object.keys(attributes).reduce(function (memo, key) {
|
|
if (attributes[key] !== undefined && (attributes[key] !== null || keepNull)) {
|
|
memo[key] = attributes[key];
|
|
}
|
|
return memo;
|
|
}, {});
|
|
},
|
|
|
|
compose: function (a, b, keepNull) {
|
|
if (!is.object(a)) a = {};
|
|
if (!is.object(b)) b = {};
|
|
var attributes = this.clone(b, keepNull);
|
|
for (var key in a) {
|
|
if (a[key] !== undefined && b[key] === undefined) {
|
|
attributes[key] = a[key];
|
|
}
|
|
}
|
|
return Object.keys(attributes).length > 0 ? attributes : undefined;
|
|
},
|
|
|
|
diff: function(a, b) {
|
|
if (!is.object(a)) a = {};
|
|
if (!is.object(b)) b = {};
|
|
var attributes = Object.keys(a).concat(Object.keys(b)).reduce(function (attributes, key) {
|
|
if (a[key] !== b[key]) {
|
|
attributes[key] = b[key] === undefined ? null : b[key];
|
|
}
|
|
return attributes;
|
|
}, {});
|
|
return Object.keys(attributes).length > 0 ? attributes : undefined;
|
|
},
|
|
|
|
transform: function (a, b, priority) {
|
|
if (!is.object(a)) return b;
|
|
if (!is.object(b)) return undefined;
|
|
if (!priority) return b; // b simply overwrites us without priority
|
|
var attributes = Object.keys(b).reduce(function (attributes, key) {
|
|
if (a[key] === undefined) attributes[key] = b[key]; // null is a valid value
|
|
return attributes;
|
|
}, {});
|
|
return Object.keys(attributes).length > 0 ? attributes : undefined;
|
|
}
|
|
},
|
|
|
|
clone: function (op) {
|
|
var newOp = this.attributes.clone(op);
|
|
if (is.object(newOp.attributes)) {
|
|
newOp.attributes = this.attributes.clone(newOp.attributes, true);
|
|
}
|
|
return newOp;
|
|
},
|
|
|
|
iterator: function (ops) {
|
|
return new Iterator(ops);
|
|
},
|
|
|
|
length: function (op) {
|
|
if (is.number(op['delete'])) {
|
|
return op['delete'];
|
|
} else if (is.number(op.retain)) {
|
|
return op.retain;
|
|
} else {
|
|
return is.string(op.insert) ? op.insert.length : 1;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
function Iterator(ops) {
|
|
this.ops = ops;
|
|
this.index = 0;
|
|
this.offset = 0;
|
|
};
|
|
|
|
Iterator.prototype.hasNext = function () {
|
|
return this.peekLength() < Infinity;
|
|
};
|
|
|
|
Iterator.prototype.next = function (length) {
|
|
if (!length) length = Infinity;
|
|
var nextOp = this.ops[this.index];
|
|
if (nextOp) {
|
|
var offset = this.offset;
|
|
var opLength = lib.length(nextOp)
|
|
if (length >= opLength - offset) {
|
|
length = opLength - offset;
|
|
this.index += 1;
|
|
this.offset = 0;
|
|
} else {
|
|
this.offset += length;
|
|
}
|
|
if (is.number(nextOp['delete'])) {
|
|
return { 'delete': length };
|
|
} else {
|
|
var retOp = {};
|
|
if (nextOp.attributes) {
|
|
retOp.attributes = nextOp.attributes;
|
|
}
|
|
if (is.number(nextOp.retain)) {
|
|
retOp.retain = length;
|
|
} else if (is.string(nextOp.insert)) {
|
|
retOp.insert = nextOp.insert.substr(offset, length);
|
|
} else {
|
|
// offset should === 0, length should === 1
|
|
retOp.insert = nextOp.insert;
|
|
}
|
|
return retOp;
|
|
}
|
|
} else {
|
|
return { retain: Infinity };
|
|
}
|
|
};
|
|
|
|
Iterator.prototype.peekLength = function () {
|
|
if (this.ops[this.index]) {
|
|
// Should never return 0 if our index is being managed correctly
|
|
return lib.length(this.ops[this.index]) - this.offset;
|
|
} else {
|
|
return Infinity;
|
|
}
|
|
};
|
|
|
|
Iterator.prototype.peekType = function () {
|
|
if (this.ops[this.index]) {
|
|
if (is.number(this.ops[this.index]['delete'])) {
|
|
return 'delete';
|
|
} else if (is.number(this.ops[this.index].retain)) {
|
|
return 'retain';
|
|
} else {
|
|
return 'insert';
|
|
}
|
|
}
|
|
return 'retain';
|
|
};
|
|
|
|
|
|
module.exports = lib;
|