UNPKG

crafity-core

Version:
590 lines (544 loc) 15.1 kB
/*jslint node: true, bitwise: true, unparam: true, maxerr: 50, white: true */ "use strict"; /*! * crafity-core - Query helpers * Copyright(c) 2013 Crafity * Copyright(c) 2013 Bart Riemens * Copyright(c) 2013 Galina Slavova * MIT Licensed */ var crafity = module.exports; crafity.getType = (function init() { var functioNameRegEx = /function (\w{1,})\(/; return function getTypeInternal(o) { if (o === undefined) { return "undefined"; } if (o instanceof Array) { return "Array"; } if (o === null) { return "Object"; } var matches = o.constructor.toString().match(functioNameRegEx), type; if (matches !== undefined && matches !== null && matches.length === 2) { return matches[1]; } type = (typeof o); if (type === "object") { matches = o.constructor.toString().match(/(?:\[object\s)?(\w+)\]/i); if (matches.length === 2) { type = matches[1]; } } return type; }; }()); crafity.ofType = function (value, types) { types = crafity.ensureArray(types); var type = crafity.getType(value); return (types.has(type)); }; crafity.defaultValue = function (value, defaultValue) { if (value === undefined || value === null) { return defaultValue; } return value; }; crafity.hasValue = function (value) { if (value === undefined || value === null) { return false; } return true; }; crafity.hasNoValue = function (value) { if (value === undefined || value === null) { return true; } return false; }; crafity.loop = function (times, func) { var index; for (index = 0; index < times; index += 1) { func(index); } }; /** * The Enumerator base class * @class Enumerator * @param {Function} getCurrent Function that retrieves the current value * @param {Function} moveNext Function that moves to the next item and returns true if there was a next item, otherwise false * @param {Function} reset Function that resets the enumerator to the beginning of the enumerable */ function Enumerator(getCurrent, moveNext, reset) { var index = 0, self = this; /** * Get the current index of the enumerator */ this.getIndex = function getIndexEnumeratorInternal() { return index; }; /** * Function that retrieves the current value * @function * @returns {Object} The current item */ this.getCurrent = function getCurrentInternal() { return getCurrent(self); }; this.overrideGetCurrent = function overrideGetCurrentInternal(getCurrent) { self.getCurrent = function () { return getCurrent(self.getCurrent()); }; return self; }; /** * Function that moves to the next item and returns true if there was a next item, otherwise false * @function * @returns {Boolean} True if moved to the next item, otherwise false */ this.moveNext = function moveNextInternal() { index = index + 1; return moveNext(self); }; /** * Function that moves to the next item and returns true if there was a next item, otherwise false * @function * @returns {Boolean} True if moved to the next item, otherwise false */ this.reset = function resetInternal() { index = 0; return reset(self); }; } crafity.Enumerator = Enumerator; /** * A dictionary of specific enumerator implementations */ Enumerator.enumeratorFactories = {}; /** * An enumerator implementation for an Array */ Enumerator.enumeratorFactories.Function = { getEnumerator: function getFunctionEnumeratorInternal(parent, func) { var self = this; return (function () { return new Enumerator( function getcurrentInternal() { return self.getCurrent(); }, function moveNextInternal() { var returned = false; function yieldReturn(value) { returned = true; } while (returned === false && self.moveNext()) { func(self.getCurrent(), yieldReturn); } return returned; }, function resetInternal() { return self.reset(); }); }()); } }; /** * An enumerator implementation for an Array */ Enumerator.enumeratorFactories.Array = { getEnumerator: function getArrayEnumeratorInternal(parent, array) { return (function () { var startIndex = -1, index = startIndex; return new Enumerator( function getcurrentInternal(enumerator) { //debug.log("GetCurrent"); return array[index]; }, function moveNextInternal(enumerator) { //debug.log("MoveNext"); if (index >= array.length - 1) { return false; } index += 1; return index < array.length; }, function resetInternal(enumerator) { index = startIndex; }); }()); } }; /** * An enumerator implementation for an existing Enumerable */ Enumerator.enumeratorFactories.Enumerable = { getEnumerator: function getEnumerableEnumeratorInternal(parent, enumerable) { return enumerable.getEnumerator(); } }; /** * An enumerator implementation for an existing Enumerator */ Enumerator.enumeratorFactories.Enumerator = { getEnumerator: function getEnumeratorEnumeratorInternal(parent, enumerator) { return enumerator; } }; /** * The Enumerable base class * @class Enumerable * @param {Array|Enumerable|crafity.Enumerable|Enumerator} object The enumerable source to wrap */ function Enumerable(object) { var self = this, enumeratorFactory = Enumerator.enumeratorFactories[crafity.getType(object)]; if (enumeratorFactory === undefined) { throw ("There is no Enumerator for an object of type '" + crafity.getType(object) + "'"); } /** * An enumerable object */ this.getObject = function internalGetObject() { return object; }; this.overrideEnumerator = function overrideEnumeratorInternal(getEnumerator) { self.getEnumerator = function getEnumeratorInternal() { return function () { return getEnumerator(self.getEnumerator()); }; }; return self; }; /** * Returns a function that gets an enumerator * @returns {Function} A function to get an enumerator */ this.getEnumerator = function getEnumeratorInternal() { return enumeratorFactory.getEnumerator(self, object); }; /** * Define a for each loop for an enumerable * @param {Function} consumer A yield function * @returns {Enumerable} A new enumerable containing the each logic */ this.forEach = function forEachInternal(consumer) { return crafity.Enumerable.from(consumer); }; /** * Selects or transforms an enumerable into a new enumerable * @param {Function} func A function that selects a value from an item * @returns {Enumerable} A new enumerable containing the select logic */ this.select = function selectInternal(func) { // return self.forEach( // function forEachItem(value, yieldReturn) { // yieldReturn(func(value)); // } // ); // return new Enumerable(this).overrideEnumerator(function(enumerator) { // return enumerator.overrideGetCurrent(function (current) { // return func(current); // }); // }); var innerEnumerator = self.getEnumerator(); return new Enumerable( new Enumerator( function getCurrentInternal() { return func(innerEnumerator.getCurrent()); }, function moveNextInternal() { return innerEnumerator.moveNext(); }, function resetInternal() { return innerEnumerator.reset(); } ) ); }; this.union = function unionInternal(enumerable) { var innerEnumerator = self.getEnumerator(), otherEnumerable, otherEnumerator, switched = false; return new crafity.Enumerable( new crafity.Enumerator( function getCurrentInternal() { return switched === false ? innerEnumerator.getCurrent() : otherEnumerator.getCurrent(); }, function moveNextInternal() { var result = false; if (switched === false) { result = innerEnumerator.moveNext(); if (result === false) { if (otherEnumerable === undefined) { otherEnumerable = new crafity.Enumerable(enumerable); } otherEnumerator = otherEnumerable.getEnumerator(); result = otherEnumerator.moveNext(); switched = true; } } else { result = otherEnumerator.moveNext(); } return result; }, function resetInternal() { switched = false; return innerEnumerator.reset(); } ) ); }; this.selectMany = function selectManyInternal(func) { var innerEnumerator = self.getEnumerator() , childEnumerable, childEnumerator; return new crafity.Enumerable( new crafity.Enumerator( function getCurrentInternal() { return childEnumerator.getCurrent(); }, function moveNextInternal() { var movedNext = false, finished = false, childValue; if (childEnumerator !== undefined) { movedNext = childEnumerator.moveNext(); } while (movedNext === false && finished === false) { childEnumerator = undefined; childEnumerable = undefined; finished = innerEnumerator.moveNext() === false; if (finished === false) { childValue = func(innerEnumerator.getCurrent()); childEnumerable = new crafity.Enumerable(childValue); childEnumerator = childEnumerable.getEnumerator(); } if (childEnumerator !== undefined) { movedNext = childEnumerator.moveNext(); } } return movedNext; }, function resetInternal() { childEnumerator = undefined; childEnumerable = undefined; return innerEnumerator.reset(); } ) ); }; this.recursive = function recursiveInternal(func) { var innerEnumerator = self.getEnumerator() , childEnumerable, childEnumerator; return new crafity.Enumerable( new crafity.Enumerator( function getCurrentInternal() { if (crafity.hasValue(childEnumerator)) { return childEnumerator.getCurrent(); } return innerEnumerator.getCurrent(); }, function moveNextInternal() { var movedNext = false, finished = false; if (crafity.hasValue(childEnumerable) && crafity.hasNoValue(childEnumerator)) { childEnumerator = childEnumerable.getEnumerator(); } if (crafity.hasValue(childEnumerator)) { movedNext = childEnumerator.moveNext(); } if (movedNext === false && finished === false) { childEnumerator = undefined; childEnumerable = undefined; movedNext = innerEnumerator.moveNext(); finished = !movedNext; if (finished === false) { var childValue = func(innerEnumerator.getCurrent()); childEnumerable = new crafity.Enumerable(childValue).recursive(func); } } return movedNext; }, function resetInternal() { childEnumerator = undefined; childEnumerable = undefined; return innerEnumerator.reset(); } ) ); }; /** * Where filters an enumerable by creating a new filtered enumerable * @param {Function} func A function that decides if a value is included * @returns {Enumerable} A new filtered enumerable */ this.where = function whereInternal(func) { var innerEnumerator = self.getEnumerator(); return new crafity.Enumerable( new crafity.Enumerator( function getCurrentInternal() { return innerEnumerator.getCurrent(); }, function moveNextInternal() { var moveNext = innerEnumerator.moveNext(); while (moveNext && !func(innerEnumerator.getCurrent())) { moveNext = innerEnumerator.moveNext(); } return moveNext; }, function resetInternal() { return innerEnumerator.reset(); } ) ); }; /** * Take X number of items from the enumerable * @param {Number} number Number of items to take * @returns {Enumerable} A new enumerable containing the take logic */ this.take = function takeInternal(number) { var innerEnumerator = self.getEnumerator(); return new crafity.Enumerable( new crafity.Enumerator( function getCurrentInternal() { return innerEnumerator.getCurrent(); }, function moveNextInternal() { return innerEnumerator.getIndex() < number && innerEnumerator.moveNext(); }, function resetInternal() { return innerEnumerator.reset(); } ) ); }; /** * Skip X number of items in the enumerable * @param number Number of items to skip * @returns {Enumerable} A new enumerable containing the skip logic */ this.skip = function skipInternal(number) { var innerEnumerator = self.getEnumerator(); return new Enumerable( new Enumerator( function getCurrentInternal() { return innerEnumerator.getCurrent(); }, function moveNextInternal() { while (innerEnumerator.getIndex() < number && innerEnumerator.moveNext()) { innerEnumerator.__ = 1; } return innerEnumerator.moveNext(); }, function resetInternal() { return innerEnumerator.reset(); } ) ); }; /** * Loop thru all the items in an enumerable. * @param {Function} func This function will be called for every item */ this.loop = function loopInternal(func) { var enumerator = self.getEnumerator(); enumerator.reset(); while (enumerator.moveNext()) { func(enumerator.getCurrent()); } }; /** * Aggregate an enumerable with a custom function * @param {Function} func A function containing the aggregagtion logic * @param {Object} seed The initial value to start the aggregation with * @returns {Object} The aggregated result */ this.aggregate = function aggregateInternal(func, seed) { var result = seed; self.loop(function (value) { result = func(result, value); }); return result; }; /** * Collect all the values from an enumerable into an Array * @returns {Array} An array containing all the enumerable values */ this.toArray = function toArrayInternal() { return self.aggregate(function (seed, value) { seed.push(value); return seed; }, []); }; /** * Sum all the values in an enumerable * @returns {Number} The sum of the values */ this.sum = function sumInternal() { return self.aggregate(function (seed, value) { return seed + value; }, 0); }; /** * Count all the values in an enumerable * @returns {Number} The number of the values in the enumerable */ this.count = function countInternal() { return self.aggregate(function (seed, value) { return seed + 1; }, 0); }; } crafity.Enumerable = Enumerable; Enumerable.create = Enumerable.from = function fromInternal(enumerable) { return new Enumerable(enumerable); }; /* (function loaded() { // var obj = [ // { "x": 1, "y": [1,2,3,4,5] }, // { "x": 2, "y": [6,7,8,9,10] } // ]; // // var result = new crafity.Enumerable(obj) // .union([{ "x": 3, "y": [11,12,13,14,15] }]) // .selectMany(function (o) { return o.y; }) // .sum(); // // debug.log("sum", result); var obj = [ { name: "root", children: [ { name: "branch", children: [ { name: "leave", children: [] } ] } ] } ]; obj.asEnumerable().recursive( function (o) { return o.children; }).loop(function (item) { debug.log(item.name); }); return; var numbers = new Enumerable([1, 2, 3, 4, 5]), range = numbers .skip(2) .take(3) .select( function (number) { return number + 1; }); debug.log("Numbers", numbers.toArray()); debug.log("Range", range.toArray()); debug.log("SUM", range.sum()); }()); */