UNPKG

scontainers

Version:

A container/collection/iterator library for JavaScript, comfortable to use, performant and versatile.

730 lines (578 loc) 17.9 kB
"use strict"; function _implSymbol(target, sym, value) { Object.defineProperty(target, sym, { value, configurable: true }); return target[sym]; } function _getSymbol(targetSymName, ...traitSets) { let symbol; traitSets.forEach(traitSet => { const sym = traitSet[targetSymName]; if (typeof sym === 'symbol') { if (!!symbol && symbol !== sym) { throw new Error(`Symbol ${targetSymName} offered by multiple trait sets.`); } symbol = sym; } }); if (!symbol) { throw new Error(`No trait set is providing symbol ${targetSymName}.`); } return symbol; } function _testTraitSet(traitSet) { if (!traitSet || typeof traitSet === 'boolean' || typeof traitSet === 'number' || typeof traitSet === 'string') { throw new Error(`${traitSet} cannot be used as a trait set.`); } } const { assert, traits, toStr } = require('../utils.js'); _testTraitSet(traits.utils); _testTraitSet(traits.scontainers); const _nth = _getSymbol("nth", traits.utils, traits.scontainers); const _len = _getSymbol("len", traits.utils, traits.scontainers); const _addTraitFactories = _getSymbol("addTraitFactories", traits.utils, traits.scontainers); const _setNth = _getSymbol("setNth", traits.utils, traits.scontainers); const _reverse = _getSymbol("reverse", traits.utils, traits.scontainers); const _truncate = _getSymbol("truncate", traits.utils, traits.scontainers); const _kvIterator = _getSymbol("kvIterator", traits.utils, traits.scontainers); const _kvReorderedIterator = _getSymbol("kvReorderedIterator", traits.utils, traits.scontainers); const _whileEach = _getSymbol("whileEach", traits.utils, traits.scontainers); const _forEach = _getSymbol("forEach", traits.utils, traits.scontainers); const _reduce = _getSymbol("reduce", traits.utils, traits.scontainers); const _nthKVN = _getSymbol("nthKVN", traits.utils, traits.scontainers); const _first = _getSymbol("first", traits.utils, traits.scontainers); const _find = _getSymbol("find", traits.utils, traits.scontainers); const _get = _getSymbol("get", traits.utils, traits.scontainers); const _set = _getSymbol("set", traits.utils, traits.scontainers); const _sum = _getSymbol("sum", traits.utils, traits.scontainers); const _reduceFirst = _getSymbol("reduceFirst", traits.utils, traits.scontainers); const _add = _getSymbol("add", traits.utils, traits.scontainers); const _flatten = _getSymbol("flatten", traits.utils, traits.scontainers); const _hasKey = _getSymbol("hasKey", traits.utils, traits.scontainers); const _untilEach = _getSymbol("untilEach", traits.utils, traits.scontainers); const _map = _getSymbol("map", traits.utils, traits.scontainers); const _collect = _getSymbol("collect", traits.utils, traits.scontainers); const _has = _getSymbol("has", traits.utils, traits.scontainers); const _from = _getSymbol("from", traits.utils, traits.scontainers); const _values = _getSymbol("values", traits.utils, traits.scontainers); const _wrapScontainer = _getSymbol("wrapScontainer", traits.utils, traits.scontainers); const _flattenDeep = _getSymbol("flattenDeep", traits.utils, traits.scontainers); const _filter = _getSymbol("filter", traits.utils, traits.scontainers); const _slice = _getSymbol("slice", traits.utils, traits.scontainers); const _skipWhile = _getSymbol("skipWhile", traits.utils, traits.scontainers); const _takeWhile = _getSymbol("takeWhile", traits.utils, traits.scontainers); const Values = require('../decorators/values.js'); const Entries = require('../decorators/entries.js'); const Filter = require('../decorators/filter.js'); const Slice = require('../decorators/slice.js'); const Chunk = require('../decorators/chunk.js'); const Map = require('../decorators/map.js'); const MapKey = require('../decorators/map_key.js'); const Cache = require('../decorators/cache.js'); const Iter = require('../decorators/iter.js'); const Reordered = require('../decorators/reordered.js'); const GroupBy = require('../decorators/group_by.js'); const Cow = require('../decorators/cow.js'); const Flatten = require('../decorators/flatten.js'); const Reverse = require('../decorators/reverse.js'); const SkipWhile = require('../decorators/skip_while.js'); const TakeWhile = require('../decorators/take_while.js'); function deriveProtocols() { assert(this, `deriveProtocols() must be called on an object`); const Collection = this; const proto = Collection.prototype; // checking that the collection is OK { if (proto[_nth]) { assert(proto[_len], `${Collection.name} enumerable, but unknown \`len\``); } } traits.scontainers[_addTraitFactories](proto, { // TODO: move these away from here! they're core traits... push() { if (proto[_setNth] && proto[_len]) { return function push(value) { const n = this[_len](); this[_setNth](n, value); }; } }, unshift() { if (proto[_setNth] && proto[_len]) { return function push(value) { this[_reverse]().forEach((v, k, n) => { this[_setNth](n + 1, v); }); this[_setNth](0, value); }; } }, remove() { if (proto[_setNth] && proto[_len]) { return function remove(n) { const len = this[_len](); for (let i = n; i < len; ++i) { this[_setNth](i - 1, this[_nth](i)); } this[_truncate](len - 1); }; } }, forEach() { if (proto[_kvIterator]) { return function forEach(fn) { const it = this[_kvIterator](); let next = it.next(); while (next) { const { value, key, n } = next; fn(value, key, n); next = it.next(); } }; } if (proto[_kvReorderedIterator]) { return function forEach(fn) { const rit = this[_kvReorderedIterator](); rit.onNext(({ key, value, n }) => void fn(value, key, n)); rit.proceed(); }; } }, whileEach() { if (proto[_kvIterator]) { return function forEach(fn) { const it = this[_kvIterator](); let next = it.next(); while (next) { const { value, key, n } = next; if (!fn(value, key, n)) { return next; } next = it.next(); } }; } if (proto[_kvReorderedIterator]) { return function (fn) { let result; { const rit = this[_kvReorderedIterator](); rit.onNext(kvn => { if (!fn(kvn.value, kvn.key, kvn.n)) { result = kvn; rit.stop(); } }); rit.proceed(); } return result; }; } }, untilEach() { if (proto[_whileEach]) { return function untilEach(fn) { return this[_whileEach]((value, key, n) => !fn(value, key, n)); }; } }, reduce() { if (proto[_forEach]) { return function reduce(fn, start) { let state = start; this[_forEach]((value, key, n) => { state = fn(state, value, key, n); }); return state; }; } }, reduceFirst() { if (proto[_kvIterator]) { return function reduceFirst(fn) { const it = this[_kvIterator](); let next = it.next(); if (!next) { return; } let state = next.value; next = it.next(); while (next) { const { value, key, n } = next; state = fn(state, value, key, n); next = it.next(); } return state; }; } if (proto[_reduce]) { return function reduceFirst(fn) { let first = true; return this[_reduce]((state, value, key, n) => { if (first) { first = false; return value; } return fn(state, value, key, n); }); }; } }, count() { if (proto[_len]) { return function count() { return this[_len](); }; } if (proto[_reduce]) { return function count() { return this[_reduce](n => n + 1, 0); }; } }, isEmpty() { if (proto[_len]) { return function isEmpty() { return !this[_len](); }; } if (proto[_whileEach]) { return function isEmpty() { return !this[_whileEach](() => false); }; } }, only() { if (proto[_nthKVN]) { return function only() { assert(this[_len]() === 1, `${this}.only()'s collection has ${this[_len]()} items`); return this[_nthKVN](0); }; } if (proto[_len]) { return function only() { assert(this[_len]() === 1, `${this}.only()'s collection has ${this[_len]()} items`); return this[_kvIterator]().next(); }; } if (proto[_kvIterator]) { return function only() { const it = this[_kvIterator](); let next = it.next(); assert(next, `${this}.only()'s collection is empty`); assert(!it.next(), `${this}.only()'s collection has multiple items`); return next; }; } if (proto[_whileEach]) { return function only() { let count = 0; this[_whileEach](() => { assert(!count, `${this}.only()'s collection has multiple items`); ++count; return true; }); assert(count === 1, `${this}.only()'s collection is empty`); return this[_whileEach](() => false); }; } }, first() { if (proto[_nthKVN]) { return function first() { return this[_nthKVN](0); }; } if (proto[_kvIterator]) { return function first() { return this[_kvIterator]().next(); }; } if (proto[_whileEach]) { return function first() { return this[_whileEach](() => false); }; } }, last() { if (proto[_nthKVN]) { return function last() { const n = this[_len]() - 1; return this[_nthKVN](n); }; } else if (proto[_reverse]) { return function last() { return this[_reverse]()[_first](); }; } }, random() { if (proto[_nthKVN]) { return function random() { const n = Math.floor(Math.random() * this[_len]()); return this[_nthKVN](n); }; } }, find() { if (proto[_whileEach]) { return function find(fn) { return this[_whileEach]((v, k, n) => { return !fn(v, k, n); }); }; } }, findLast(fn) { if (proto[_reverse] && proto[_find]) { return function findLast(fn) { return this[_reverse]()[_find](fn); }; } }, swapNs() { if (proto[_nth] && proto[_setNth]) { return function (n1, n2) { const value1 = this[_nth](n1); this[_setNth](n1, this[_nth](n2)); this[_setNth](n2, value1); }; } }, swapKeys() { if (proto[_get] && proto[_set]) { return function (key1, key2) { const value1 = this[_get](key1); this[_set](key1, this[_get](key2)); this[_set](key2, value1); }; } }, sum() { if (proto[_reduce]) { return function sum() { return this[_reduce]((sum, n) => sum + n, 0); }; } }, avg() { if (proto[_len]) { return function avg() { return this[_sum]() / this[_len](); }; } if (proto[_reduce]) { return function avg() { const [sum, count] = this[_reduce](([sum, count], n) => [sum + n, count + 1], [0, 0]); return sum / count; }; } }, min() { // TODO: `reduce` could be more performant in some cases... if (proto[_reduceFirst]) { return function () { return this[_reduceFirst]((min, n) => Math.min(min, n)); }; } }, max() { // TODO: `reduce` could be more performant in some cases... if (proto[_reduceFirst]) { return function () { return this[_reduceFirst]((max, n) => Math.max(max, n)); }; } }, join() { // TODO: `reduce` could be more performant in some cases... if (proto[_reduceFirst]) { return function join(sep = '') { return this[_reduceFirst]((str, substr) => str + sep + substr, ''); }; } }, assign() { if (proto[_add]) { return function assign(...collections) { return collections[_flatten]()[_forEach](value => this[_add](value)); }; } if (proto[_set]) { return function assign(...collections) { return collections[_flatten]()[_forEach]((value, key) => this[_set](key, value)); }; } }, defaults() { if (proto[_set]) { return function defaults(...collections) { return collections[_flatten]()[_forEach]((value, key) => { if (!this[_hasKey](key)) { this[_set](key, value); } }); }; } }, every() { if (proto[_whileEach]) { return function every(fn) { return !this[_whileEach](fn); }; } }, some() { if (proto[_untilEach]) { return function some(fn) { return !!this[_untilEach](fn); }; } }, toString() { if (proto[_nth]) { return function toString() { //this._Straits.map( (value)=>value::toStr() )._Straits.collect( Array ); // this._Straits.iterator(); // console.log( this._Straits.len(), this.constructor.name ); // console.log( this._Straits.kvIterator.toString() ); const out = this[_map](value => toStr.call(value))[_collect](Array); return `*[${out.join(', ')}]`; }; } if (proto[_forEach] && proto[_has]) { return function toString() { const out = this[_map](item => toStr.call(item))[_collect](Array); return `*{${out.join(', ')}}`; }; } if (proto[_forEach]) { return function toString() { const out = this[_map]((value, key) => `${toStr.call(key)}:${toStr.call(value)}`)[_collect](Array); return `*{${out.join(', ')}}`; }; } console.warn(`${Collection.name} not printable`); }, consume() { return function consume(TargetCollection) { assert(TargetCollection[_from], `No ${TargetCollection.name}.*from()`); return TargetCollection[_from](this); }; }, collect() { return function collect(TargetCollection) { assert(TargetCollection[_from], `No ${TargetCollection.name}.*from()`); return TargetCollection[_from](this); }; } }); traits.scontainers[_addTraitFactories](proto, { keys() { return function keys() { return this[_map](function key(value, key) { return key; })[_values](); }; }, values: Collection[_wrapScontainer](Values), entries: Collection[_wrapScontainer](Entries), enumerate() { return function enumerate() { global.TODO(); let count = 0; return this[_map](value => [count++, value]); }; }, filter: Collection[_wrapScontainer](Filter), slice: Collection[_wrapScontainer](Slice), chunk: Collection[_wrapScontainer](Chunk), map: Collection[_wrapScontainer](Map), mapKey: Collection[_wrapScontainer](MapKey), cache: Collection[_wrapScontainer](Cache), iter: Collection[_wrapScontainer](Iter), reordered: Collection[_wrapScontainer](Reordered), groupBy: Collection[_wrapScontainer](GroupBy), cow: Collection[_wrapScontainer](Cow), flatten: Collection[_wrapScontainer](Flatten), reverse: Collection[_wrapScontainer](Reverse), flattenDeep() { if (proto[_map]) { return function () { return this[_map](value => value[_flattenDeep] ? value[_flattenDeep]() : value)[_flatten](); }; } }, concat() { return function concat(...collections) { return [this, ...collections][_flatten](); }; }, uniq() { if (proto[_filter]) { return function uniq(eq = (a, b) => a === b) { let last = NaN; return this[_filter]((value, key) => { const result = eq(last, value); last = value; return !result; }); }; } }, skipWhile: Collection[_wrapScontainer](SkipWhile), takeWhile: Collection[_wrapScontainer](TakeWhile), skip() { if (proto[_slice]) { return function skip(n) { return this[_slice](n); }; } return function skip(n) { let counter = 0; return this[_skipWhile](() => { ++counter; return counter < n; }); }; }, take() { if (proto[_slice]) { return function take(n) { return this[_slice](0, n); }; } return function take(n) { let counter = 0; return this[_takeWhile](() => { ++counter; return counter < n; }); }; } }); } module.exports = { deriveProtocols };