377 lines
11 KiB
JavaScript
377 lines
11 KiB
JavaScript
var _ = require('lodash'),
|
|
uuid = require('uuid'),
|
|
Cursor;
|
|
|
|
/**
|
|
* @param {Number} [length=0]
|
|
* @param {Number} [cycles=1]
|
|
* @param {Number} [position=0]
|
|
* @param {Number} [iteration=0]
|
|
* @param {String} [ref]
|
|
* @constructor
|
|
*/
|
|
Cursor = function RunCursor (length, cycles, position, iteration, ref) { // eslint-disable-line func-name-matching
|
|
this.length = Cursor.validate(length, 0);
|
|
this.position = Cursor.validate(position, 0, this.length);
|
|
|
|
this.cycles = Cursor.validate(cycles, 1, 1);
|
|
this.iteration = Cursor.validate(iteration, 0, this.cycles);
|
|
|
|
this.ref = ref || uuid.v4();
|
|
};
|
|
|
|
_.assign(Cursor.prototype, {
|
|
/**
|
|
*
|
|
*
|
|
* @param {Object} state
|
|
* @param {Number} [state.length=0]
|
|
* @param {Number} [state.cycles=1]
|
|
* @param {Number} [state.position=0]
|
|
* @param {Number} [state.iteration=0]
|
|
* @param {String} [state.ref]
|
|
* @param {Function} [callback] - receives `(err:Error, coords:Object, previous:Object)`
|
|
* @param {Object} [scope]
|
|
*/
|
|
load: function (state, callback, scope) {
|
|
!state && (state = {});
|
|
(state instanceof Cursor) && (state = state.current());
|
|
|
|
this.reset(state.length, state.cycles, state.position, state.iteration, state.ref, callback, scope);
|
|
},
|
|
|
|
/**
|
|
* Update length and cycle bounds
|
|
*
|
|
* @param {Number} [length=0]
|
|
* @param {Number} [cycles=1]
|
|
* @param {Number} [position=0]
|
|
* @param {Number} [iteration=0]
|
|
* @param {String} [ref]
|
|
* @param {Function} [callback] - receives `(err:Error, coords:Object, previous:Object)`
|
|
* @param {Object} [scope]
|
|
*/
|
|
reset: function (length, cycles, position, iteration, ref, callback, scope) {
|
|
var coords = _.isFunction(callback) && this.current();
|
|
|
|
// validate parameter defaults
|
|
_.isNil(length) && (length = this.length);
|
|
_.isNil(cycles) && (cycles = this.cycles);
|
|
_.isNil(position) && (position = this.position);
|
|
_.isNil(iteration) && (iteration = this.iteration);
|
|
_.isNil(ref) && (ref = this.ref);
|
|
|
|
// use the constructor to set the values
|
|
Cursor.call(this, length, cycles, position, iteration, ref);
|
|
|
|
// send before and after values to the callback
|
|
return coords && callback.call(scope || this, null, this.current(), coords);
|
|
},
|
|
|
|
/**
|
|
* Update length and cycle bounds
|
|
*
|
|
* @param {Number} [length=0]
|
|
* @param {Number} [cycles=1]
|
|
* @param {Function} [callback] - receives `(err:Error, coords:Object, previous:Object)`
|
|
* @param {Object} [scope]
|
|
*/
|
|
bounds: function (length, cycles, callback, scope) {
|
|
var coords = _.isFunction(callback) && this.current();
|
|
|
|
// validate parameter defaults
|
|
_.isNil(length) && (length = this.length);
|
|
_.isNil(cycles) && (cycles = this.cycles);
|
|
|
|
// use the constructor to set the values
|
|
Cursor.call(this, length, cycles, this.position, this.iteration);
|
|
|
|
return coords && callback.call(scope || this, null, this.current(), coords);
|
|
},
|
|
|
|
/**
|
|
* Set everything to minimum dimension
|
|
*
|
|
* @param {Function} [callback] - receives `(err:Error, coords:Object, previous:Object)`
|
|
* @param {Object} [scope]
|
|
*/
|
|
zero: function (callback, scope) {
|
|
var coords = _.isFunction(callback) && this.current();
|
|
|
|
this.position = 0;
|
|
this.iteration = 0;
|
|
|
|
// send before and after values to the callback
|
|
return coords && callback.call(scope || this, null, this.current(), coords);
|
|
},
|
|
|
|
/**
|
|
* Set everything to mnimum dimension
|
|
*
|
|
* @param {Function} [callback] - receives `(err:Error, coords:Object, previous:Object)`
|
|
* @param {Object} [scope]
|
|
*/
|
|
clear: function (callback, scope) {
|
|
var coords = _.isFunction(callback) && this.current();
|
|
|
|
this.position = 0;
|
|
this.iteration = 0;
|
|
this.cycles = 1;
|
|
this.length = 0;
|
|
|
|
return coords && callback.call(scope || this, null, this.current(), coords);
|
|
},
|
|
|
|
/**
|
|
* Seek to a specified Cursor
|
|
*
|
|
* @param {Number} [position]
|
|
* @param {Number} [iteration]
|
|
* @param {Function} [callback] - receives `(err:Error, changed:Boolean, coords:Object, previous:Object)`
|
|
* @param {Object} [scope]
|
|
*/
|
|
seek: function (position, iteration, callback, scope) {
|
|
var coords = _.isFunction(callback) && this.current();
|
|
|
|
// if null or undefined implies use existing seek position
|
|
_.isNil(position) && (position = this.position);
|
|
_.isNil(iteration) && (iteration = this.iteration);
|
|
|
|
// make the pointers stay within boundary
|
|
if ((position >= this.length) || (iteration >= this.cycles) || (position < 0) || (iteration < 0) ||
|
|
isNaN(position) || isNaN(iteration)) {
|
|
return coords &&
|
|
callback.call(scope || this, new Error('runcursor: seeking out of bounds: ' + [position, iteration]));
|
|
}
|
|
|
|
// floor the numbers
|
|
position = ~~position;
|
|
iteration = ~~iteration;
|
|
|
|
// set the new positions
|
|
this.position = Cursor.validate(position, 0, this.length);
|
|
this.iteration = Cursor.validate(iteration, 0, this.cycles);
|
|
|
|
// finally execute the callback with the seek position
|
|
return coords && callback.call(scope || this, null, this.hasChanged(coords), this.current(), coords);
|
|
},
|
|
|
|
/**
|
|
* Seek one forward
|
|
*
|
|
* @param {Function} [callback] - receives `(err:Error, changed:Boolean, coords:Object, previous:Object)`
|
|
* @param {Object} [scope]
|
|
*/
|
|
next: function (callback, scope) {
|
|
var position = this.position,
|
|
iteration = this.iteration,
|
|
|
|
coords;
|
|
|
|
// increment position
|
|
position += 1;
|
|
|
|
// check if we need to increment cycle
|
|
if (position >= this.length) {
|
|
// set position to 0 and increment iteration
|
|
position = 0;
|
|
iteration += 1;
|
|
|
|
if (iteration >= this.cycles) {
|
|
coords = _.isFunction(callback) && this.current();
|
|
coords.eof = true;
|
|
|
|
return coords && callback.call(scope || this, null, false, coords, coords);
|
|
}
|
|
|
|
coords && (coords.cr = true);
|
|
}
|
|
|
|
// finally handover the new coordinates to seek function
|
|
return this.seek(position, iteration, callback, scope);
|
|
},
|
|
|
|
/**
|
|
* Tentative Cursor status, if we do `.next()`
|
|
*
|
|
* @param {Object} coords
|
|
*
|
|
* @returns {Object}
|
|
*/
|
|
whatnext: function (coords) {
|
|
var base = {
|
|
ref: this.ref,
|
|
length: this.length,
|
|
cycles: this.cycles
|
|
},
|
|
position,
|
|
iteration;
|
|
|
|
if (!_.isObject(coords)) {
|
|
return _.assign(base, {eof: true, bof: true, empty: this.empty()});
|
|
}
|
|
if (!this.length) {
|
|
return _.assign(base, {eof: true, bof: true, empty: true});
|
|
}
|
|
|
|
position = coords.position;
|
|
iteration = coords.iteration;
|
|
|
|
// increment position
|
|
position += 1;
|
|
|
|
// check if we need to increment cycle
|
|
if (position >= this.length) {
|
|
// set position to 0 and increment iteration
|
|
position = 0;
|
|
iteration += 1;
|
|
|
|
if (iteration >= this.cycles) {
|
|
return _.assign(base, {
|
|
position: this.length - 1,
|
|
iteration: iteration - 1,
|
|
eof: true
|
|
});
|
|
}
|
|
|
|
return _.assign(base, {
|
|
position: position,
|
|
iteration: iteration,
|
|
cr: true
|
|
});
|
|
}
|
|
|
|
return _.assign(base, {position: position, iteration: iteration});
|
|
},
|
|
|
|
/**
|
|
* Check whether current position and iteration is not as the same specified
|
|
*
|
|
* @param {Object} coords
|
|
* @returns {Boolean}
|
|
*/
|
|
hasChanged: function (coords) {
|
|
return _.isObject(coords) && !((this.position === coords.position) && (this.iteration === coords.iteration));
|
|
},
|
|
|
|
/**
|
|
* Current Cursor state
|
|
*
|
|
* @returns {Object}
|
|
*/
|
|
current: function () {
|
|
return {
|
|
position: this.position,
|
|
iteration: this.iteration,
|
|
length: this.length,
|
|
cycles: this.cycles,
|
|
empty: this.empty(),
|
|
eof: this.eof(),
|
|
bof: this.bof(),
|
|
cr: this.cr(),
|
|
ref: this.ref
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Is the current position going to trigger a new iteration on `.next`?
|
|
*
|
|
* @returns {Boolean}
|
|
*/
|
|
cr: function () {
|
|
return !this.length || (this.position >= this.length);
|
|
},
|
|
|
|
/**
|
|
* @returns {Boolean}
|
|
*/
|
|
eof: function () {
|
|
return !this.length || (this.position >= this.length) && (this.iteration >= this.cycles);
|
|
},
|
|
|
|
/**
|
|
* @returns {Boolean}
|
|
*/
|
|
bof: function () {
|
|
return !this.length || ((this.position === 0) && (this.iteration === 0));
|
|
},
|
|
|
|
/**
|
|
* @returns {Boolean}
|
|
*/
|
|
empty: function () {
|
|
return !this.length;
|
|
},
|
|
|
|
/**
|
|
* @returns {Object}
|
|
*/
|
|
valueOf: function () {
|
|
return this.current();
|
|
},
|
|
|
|
clone: function () {
|
|
return new Cursor(this.length, this.cycles, this.position, this.iteration);
|
|
}
|
|
});
|
|
|
|
_.assign(Cursor, {
|
|
/**
|
|
* @param {Number} [length=0]
|
|
* @param {Number} [cycles=1]
|
|
* @param {Number} [position=0]
|
|
* @param {Number} [iteration=0]
|
|
* @param {String} [ref]
|
|
*
|
|
* @returns {Number}
|
|
*/
|
|
create: function (length, cycles, position, iteration, ref) {
|
|
return new Cursor(length, cycles, position, iteration, ref);
|
|
},
|
|
|
|
/**
|
|
* @param {Object|Cursor} obj
|
|
* @param {Object} [bounds]
|
|
* @param {Number} [bounds.length]
|
|
* @param {Number} [bounds.cycles]
|
|
*
|
|
* @returns {Cursor}
|
|
*/
|
|
box: function (obj, bounds) {
|
|
// already a Cursor, do nothing
|
|
if (obj instanceof Cursor) {
|
|
bounds && obj.bounds(bounds.length, bounds.cycles);
|
|
|
|
return obj;
|
|
}
|
|
|
|
// nothing to box, create a blank Cursor
|
|
if (!_.isObject(obj)) { return new Cursor(bounds && bounds.length, bounds && bounds.cycles); }
|
|
|
|
// load Cursor values from object
|
|
return new Cursor((bounds || obj).length, (bounds || obj).cycles, obj.position, obj.iteration, obj.ref);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*
|
|
* @param {Number} num
|
|
* @param {Number} min [description]
|
|
* @param {Number} [max]
|
|
*
|
|
* @returns {Number}
|
|
*/
|
|
validate: function (num, min, max) {
|
|
if (typeof num !== 'number' || num < min) {
|
|
return min;
|
|
}
|
|
if (num === Infinity) {
|
|
return _.isNil(max) ? min : max;
|
|
}
|
|
|
|
return num;
|
|
}
|
|
});
|
|
|
|
module.exports = Cursor;
|