'use strict' class FiggyPudding { constructor (specs, opts, providers) { this.__specs = specs || {} Object.keys(this.__specs).forEach(alias => { if (typeof this.__specs[alias] === 'string') { const key = this.__specs[alias] const realSpec = this.__specs[key] if (realSpec) { const aliasArr = realSpec.aliases || [] aliasArr.push(alias, key) realSpec.aliases = [...(new Set(aliasArr))] this.__specs[alias] = realSpec } else { throw new Error(`Alias refers to invalid key: ${key} -> ${alias}`) } } }) this.__opts = opts || {} this.__providers = reverse((providers).filter( x => x != null && typeof x === 'object' )) this.__isFiggyPudding = true } get (key) { return pudGet(this, key, true) } get [Symbol.toStringTag] () { return 'FiggyPudding' } forEach (fn, thisArg = this) { for (let [key, value] of this.entries()) { fn.call(thisArg, value, key, this) } } toJSON () { const obj = {} this.forEach((val, key) => { obj[key] = val }) return obj } * entries (_matcher) { for (let key of Object.keys(this.__specs)) { yield [key, this.get(key)] } const matcher = _matcher || this.__opts.other if (matcher) { const seen = new Set() for (let p of this.__providers) { const iter = p.entries ? p.entries(matcher) : Object.entries(p) for (let [key, val] of iter) { if (matcher(key) && !seen.has(key)) { seen.add(key) yield [key, val] } } } } } * [Symbol.iterator] () { for (let [key, value] of this.entries()) { yield [key, value] } } * keys () { for (let [key] of this.entries()) { yield key } } * values () { for (let [, value] of this.entries()) { yield value } } concat (...moreConfig) { return new Proxy(new FiggyPudding( this.__specs, this.__opts, reverse(this.__providers).concat(moreConfig) ), proxyHandler) } } try { const util = require('util') FiggyPudding.prototype[util.inspect.custom] = function (depth, opts) { return ( this[Symbol.toStringTag] + ' ' ) + util.inspect(this.toJSON(), opts) } } catch (e) {} function BadKeyError (key) { throw Object.assign(new Error( `invalid config key requested: ${key}` ), {code: 'EBADKEY'}) } function pudGet (pud, key, validate) { let spec = pud.__specs[key] if (validate && !spec && (!pud.__opts.other || !pud.__opts.other(key))) { BadKeyError(key) } else { if (!spec) { spec = {} } let ret for (let p of pud.__providers) { ret = tryGet(key, p) if (ret === undefined && spec.aliases && spec.aliases.length) { for (let alias of spec.aliases) { if (alias === key) { continue } ret = tryGet(alias, p) if (ret !== undefined) { break } } } if (ret !== undefined) { break } } if (ret === undefined && spec.default !== undefined) { if (typeof spec.default === 'function') { return spec.default(pud) } else { return spec.default } } else { return ret } } } function tryGet (key, p) { let ret if (p.__isFiggyPudding) { ret = pudGet(p, key, false) } else if (typeof p.get === 'function') { ret = p.get(key) } else { ret = p[key] } return ret } const proxyHandler = { has (obj, prop) { return prop in obj.__specs && pudGet(obj, prop, false) !== undefined }, ownKeys (obj) { return Object.keys(obj.__specs) }, get (obj, prop) { if ( typeof prop === 'symbol' || prop.slice(0, 2) === '__' || prop in FiggyPudding.prototype ) { return obj[prop] } return obj.get(prop) }, set (obj, prop, value) { if ( typeof prop === 'symbol' || prop.slice(0, 2) === '__' ) { obj[prop] = value return true } else { throw new Error('figgyPudding options cannot be modified. Use .concat() instead.') } }, deleteProperty () { throw new Error('figgyPudding options cannot be deleted. Use .concat() and shadow them instead.') } } module.exports = figgyPudding function figgyPudding (specs, opts) { function factory (...providers) { return new Proxy(new FiggyPudding( specs, opts, providers ), proxyHandler) } return factory } function reverse (arr) { const ret = [] arr.forEach(x => ret.unshift(x)) return ret }