units
Version:
Module-like system with two-step initialization
269 lines (219 loc) • 5.39 kB
JavaScript
const handler = require('./proxy_handler');
const UNITS = Symbol('Units');
const PATH = '.';
const SELF = '@';
const EXPOSE = '@expose';
const EXTEND = '@extend';
const isUnit = value => value.init || Array.isArray(value) || typeof value !== 'object';
class Units {
constructor(units) {
this.inited = false;
this[UNITS] = {};
this.proxy = new Proxy(this, handler);
units && this.add(units);
}
add(arg1, arg2) {
if (arg2 === undefined) {
return this.addObject(arg1);
}
// If arg1 is the units
const units = this.getUnits(arg1);
if (units) {
return units.add(arg2);
}
// If arg2 is a units instance.
if (arg2[UNITS]) {
this._add(arg1, arg2);
return this;
}
const type = typeof arg2;
const isFunc2 = type === 'function';
if (!arg2.init && (type === 'object' || isFunc2)) {
const units = isFunc2 ? arg2(this.proxy) : arg2;
this._add(arg1, units);
return this;
}
this._add(arg1, arg2);
return this;
}
addObject(obj) {
const typeofObj = typeof obj;
if (Array.isArray(obj) || !(typeofObj === 'object' || typeofObj === 'function')) {
throw new Error(`Unit '${obj}'' can not be undefined`);
}
const units = typeof obj === 'function' ? obj(this.proxy) : obj;
if (units[EXTEND]) {
return this.extend(units);
}
if (units[EXPOSE]) {
return this.expose(units);
}
for (const key in units) {
this.add(key, units[key]);
}
return this;
}
_add(key, obj) {
if (this[UNITS][key]) {
throw new Error(`Units duplicate key: ${key}`);
}
let units;
if (key === SELF) {
units = obj;
} else if (obj instanceof Units) {
units = obj;
units.parent = this;
} else {
units = new Units();
units.parent = this;
if (isUnit(obj)) {
units._add(SELF, obj);
} else {
units.addObject(obj);
}
}
this[UNITS][key] = units;
return this[UNITS][key];
}
join(units) {
for (const key in units[UNITS]) {
this._add(key, units[UNITS][key]);
}
}
extend(obj) {
delete obj[EXTEND];
const unit = this[UNITS][SELF];
if (typeof unit !== 'object') {
return this.expose(obj);
}
for (const prop in obj) {
unit[prop] = obj[prop];
}
return this;
}
expose(obj) {
delete obj[EXPOSE];
this._add(SELF, obj);
return this;
}
alias(aliasKey, srcKey) {
const srcUnit = this.getUnits(srcKey);
if (srcUnit === undefined) {
throw new Error('Unit is required: ' + srcKey);
}
if (this[UNITS][aliasKey]) {
throw new Error(`Unit with the key '${aliasKey}' is already exists`);
}
this[UNITS][aliasKey] = srcUnit;
}
forEach(cb, ctx = this) {
for (const key of this) {
cb.call(ctx, this.get(key), key);
}
}
match(rx, cb, ctx = this) {
if (typeof rx === 'string') {
rx = new RegExp(rx);
}
for (const key of this) {
const match = key.match(rx);
if (match) {
const [ _, ...args ] = match;
cb.apply(ctx, [ this.get(key) ].concat(args))
}
}
}
require(key) {
const unit = this.get(key);
if (unit === undefined) {
throw new Error('Unit is required: ' + key);
}
return unit;
}
has(key = SELF) {
return this.getUnits(key) !== undefined;
}
isEmpty() {
return !Object.keys(this[UNITS]).length
}
get(key = SELF) {
const units = this.getUnits(key);
if (units !== undefined) {
return key === SELF ? this._get() : units._get();
}
if (key !== SELF && this.parent) {
return this.parent.get(key);
}
}
_get() {
const unit = this[UNITS][SELF];
if (unit === undefined) {
return this;
}
if (!this.inited && unit.initRequired) {
this.init();
}
if (unit.instance) {
if (typeof unit.instance === 'function') {
return unit.instance();
}
return unit.instance;
}
return unit;
}
getUnits(key) {
const path = key.split(PATH);
let result = this;
for (const i in path) {
result = result[UNITS][path[i]];
if (!result) {
return;
}
}
return result;
}
init() {
if (this.inited) {
return;
}
this.inited = true;
for (const key in this[UNITS]) {
const unit = this[UNITS][key];
if (key === SELF) {
unit.init && unit.init(this.proxy);
} else {
this[UNITS][key].init();
}
}
}
[Symbol.iterator]() {
const units = this[UNITS];
const iter = Object.keys(units)[Symbol.iterator]();
let childrenIter;
let path;
const iterator = {
next: parent => {
if (childrenIter) {
const child = childrenIter.next(path);
if (child.done) {
childrenIter = undefined;
} else {
return { value: child.value };
}
}
const { done, value } = iter.next();
if (done) {
return { done: true };
}
path = parent ? `${parent}.${value}` : value;
if (value === SELF) {
return iterator.next();
}
childrenIter = units[value][Symbol.iterator]();
return { value: path };
}
}
return iterator;
}
}
module.exports = Units;