scontainers
Version:
A container/collection/iterator library for JavaScript, comfortable to use, performant and versatile.
324 lines (229 loc) • 7.3 kB
JavaScript
;
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 straits = require('straits');
const {
assert,
id
} = require('../utils_light.js');
_testTraitSet(straits.core);
_testTraitSet(straits.utils);
const _borrowTraits = _getSymbol("borrowTraits", straits.core, straits.utils);
const utilTraits = straits.utils.TraitSet.fromKeys({
extractKeys(keys) {},
defaultGet(key, defaultConstructor) {},
addTraits(target, implementationObj) {},
addTraitFactories(target, factoryObj) {},
implScontainer(implementationObj) {},
wrapScontainer(decoratorFactoryFactory) {},
// return a function factory for the decorator `decoratorFactory` built on `this`
describeScontainer() {},
implCoreGenerators() {},
implCoreTraits() {},
deriveTraits() {}
});
utilTraits[_borrowTraits](straits.utils, [id`asFreeFunction`, id`asMethod`, id`impl`, id`implTraits`]);
utilTraits[_borrowTraits](straits.common, [id`toString`]);
_testTraitSet(utilTraits);
const _impl = _getSymbol("impl", straits.core, straits.utils, utilTraits);
const _addTraitFactories2 = _getSymbol("addTraitFactories", straits.core, straits.utils, utilTraits);
const _addTraits = _getSymbol("addTraits", straits.core, straits.utils, utilTraits);
const _defaultGet = _getSymbol("defaultGet", straits.core, straits.utils, utilTraits);
const _extractKeys = _getSymbol("extractKeys", straits.core, straits.utils, utilTraits);
// remove all the `keys` from `this`, and return an object made of all the removed keys and their values.
_implSymbol(Object.prototype, _extractKeys, function (keys) {
const result = {};
keys.forEach(key => {
result[key] = this[key];
delete this[key];
});
return result;
});
_implSymbol(Map.prototype, _defaultGet, function (key, defaultConstructor) {
if (this.has(key)) {
return this.get(key);
} else {
const value = defaultConstructor(key);
this.set(key, value);
return value;
}
}); // like `implTraits`, but without overriding already implemented traits
_implSymbol(Object.prototype, _addTraits, function (target, implementationObj) {
for (let name in implementationObj) {
const sym = this[name];
assert(typeof sym === 'symbol', `No trait \`${name}\``);
if (target.hasOwnProperty(sym)) {
// trait already implemented; skipping
continue;
}
sym[_impl](target, implementationObj[name]);
}
});
class Factory {
constructor(factory, target) {
this.target = target;
if (!factory) {
this.factory = function () {
throw new Error(`No factory`);
};
this.assignImmediately = false;
this.canProduce = () => false;
} else if (typeof factory === 'function') {
this.factory = factory;
this.assignImmediately = false;
this.canProduce = function () {
return this.factory.call(target) !== undefined;
};
} else {
this.factory = factory.factory;
this.assignImmediately = factory.assignImmediately || false;
this.canProduce = factory.canProduce || (() => true);
}
}
produce() {
const {
target
} = this;
const fn = this.factory.call(target);
assert(fn !== undefined, `A factory didn't produce anything`);
fn.factory = function () {
return fn;
};
return fn;
}
} // `factoryObj` has symbol names as traits and factories as
_implSymbol(Object.prototype, _addTraitFactories2, function (target, factoryObj) {
for (let name in factoryObj) {
const factory = new Factory(factoryObj[name], target);
if (!factory.canProduce()) {
// factory can't produce anything
continue;
}
const sym = this[name];
assert(typeof sym === 'symbol', `No trait \`${name}\``);
if (target.hasOwnProperty(sym)) {
// trait already implemented; skipping
continue;
}
if (factory.assignImmediately) {
const fn = factory.produce();
sym[_impl](target, fn);
} else {
const lazyFn = function () {
const fn = factory.produce();
sym[_impl](target, fn);
return this[sym](...arguments);
};
lazyFn.factory = function () {
const fn = factory.produce();
sym[_impl](target, fn);
return fn;
};
sym[_impl](target, lazyFn);
}
}
});
module.exports = utilTraits;
if (require.main === module) {
const traits = new straits.utils.TraitSet('a', 'b', 'c', 'x', 'y', 'z');
_testTraitSet(traits);
const _addTraitFactories = _getSymbol("addTraitFactories", straits.core, straits.utils, utilTraits, traits);
const _a = _getSymbol("a", straits.core, straits.utils, utilTraits, traits);
const _b = _getSymbol("b", straits.core, straits.utils, utilTraits, traits);
const _c = _getSymbol("c", straits.core, straits.utils, utilTraits, traits);
const _x = _getSymbol("x", straits.core, straits.utils, utilTraits, traits);
const _y = _getSymbol("y", straits.core, straits.utils, utilTraits, traits);
const _z = _getSymbol("z", straits.core, straits.utils, utilTraits, traits);
const logs = [];
const log = function (arg) {
logs.push(arg);
}; // checks and clears logs
const checkLogs = function (...expectedLogs) {
assert.deepStrictEqual(logs, expectedLogs);
logs.length = 0;
};
const obj = {};
const factoryObj = {
a() {
log(`a factory`);
return function () {
log(`a`);
};
},
b() {
log(`b factory`);
},
c: null,
x: {
assignImmediately: true,
factory() {
log(`x factory`);
return function () {
log(`x`);
};
}
},
y: {
factory() {
log(`y factory`);
return function () {
log(`y`);
};
}
},
z: {
factory() {
log(`z factory`);
return function () {
log(`z`);
};
},
canProduce() {
log(`z can produce`);
return false;
}
}
}; // nothing was logged until now
checkLogs(); // so far the non-object factories were just tested to see whether they're valid
traits[_addTraitFactories](obj, factoryObj);
checkLogs(`a factory`, `b factory`, `x factory`, `z can produce`);
obj[_a]();
checkLogs(`a factory`, `a`);
obj[_a]();
checkLogs(`a`);
assert.throws(() => obj[_b]());
assert.throws(() => obj[_c]());
obj[_x]();
checkLogs(`x`);
obj[_y]();
checkLogs(`y factory`, `y`);
obj[_y]();
checkLogs(`y`);
assert.throws(() => obj[_z]());
}