1'use strict' 2 3class FiggyPudding { 4 constructor (specs, opts, providers) { 5 this.__specs = specs || {} 6 Object.keys(this.__specs).forEach(alias => { 7 if (typeof this.__specs[alias] === 'string') { 8 const key = this.__specs[alias] 9 const realSpec = this.__specs[key] 10 if (realSpec) { 11 const aliasArr = realSpec.aliases || [] 12 aliasArr.push(alias, key) 13 realSpec.aliases = [...(new Set(aliasArr))] 14 this.__specs[alias] = realSpec 15 } else { 16 throw new Error(`Alias refers to invalid key: ${key} -> ${alias}`) 17 } 18 } 19 }) 20 this.__opts = opts || {} 21 this.__providers = reverse((providers).filter( 22 x => x != null && typeof x === 'object' 23 )) 24 this.__isFiggyPudding = true 25 } 26 get (key) { 27 return pudGet(this, key, true) 28 } 29 get [Symbol.toStringTag] () { return 'FiggyPudding' } 30 forEach (fn, thisArg = this) { 31 for (let [key, value] of this.entries()) { 32 fn.call(thisArg, value, key, this) 33 } 34 } 35 toJSON () { 36 const obj = {} 37 this.forEach((val, key) => { 38 obj[key] = val 39 }) 40 return obj 41 } 42 * entries (_matcher) { 43 for (let key of Object.keys(this.__specs)) { 44 yield [key, this.get(key)] 45 } 46 const matcher = _matcher || this.__opts.other 47 if (matcher) { 48 const seen = new Set() 49 for (let p of this.__providers) { 50 const iter = p.entries ? p.entries(matcher) : entries(p) 51 for (let [key, val] of iter) { 52 if (matcher(key) && !seen.has(key)) { 53 seen.add(key) 54 yield [key, val] 55 } 56 } 57 } 58 } 59 } 60 * [Symbol.iterator] () { 61 for (let [key, value] of this.entries()) { 62 yield [key, value] 63 } 64 } 65 * keys () { 66 for (let [key] of this.entries()) { 67 yield key 68 } 69 } 70 * values () { 71 for (let [, value] of this.entries()) { 72 yield value 73 } 74 } 75 concat (...moreConfig) { 76 return new Proxy(new FiggyPudding( 77 this.__specs, 78 this.__opts, 79 reverse(this.__providers).concat(moreConfig) 80 ), proxyHandler) 81 } 82} 83try { 84 const util = require('util') 85 FiggyPudding.prototype[util.inspect.custom] = function (depth, opts) { 86 return ( 87 this[Symbol.toStringTag] + ' ' 88 ) + util.inspect(this.toJSON(), opts) 89 } 90} catch (e) {} 91 92function BadKeyError (key) { 93 throw Object.assign(new Error( 94 `invalid config key requested: ${key}` 95 ), {code: 'EBADKEY'}) 96} 97 98function pudGet (pud, key, validate) { 99 let spec = pud.__specs[key] 100 if (validate && !spec && (!pud.__opts.other || !pud.__opts.other(key))) { 101 BadKeyError(key) 102 } else { 103 if (!spec) { spec = {} } 104 let ret 105 for (let p of pud.__providers) { 106 ret = tryGet(key, p) 107 if (ret === undefined && spec.aliases && spec.aliases.length) { 108 for (let alias of spec.aliases) { 109 if (alias === key) { continue } 110 ret = tryGet(alias, p) 111 if (ret !== undefined) { 112 break 113 } 114 } 115 } 116 if (ret !== undefined) { 117 break 118 } 119 } 120 if (ret === undefined && spec.default !== undefined) { 121 if (typeof spec.default === 'function') { 122 return spec.default(pud) 123 } else { 124 return spec.default 125 } 126 } else { 127 return ret 128 } 129 } 130} 131 132function tryGet (key, p) { 133 let ret 134 if (p.__isFiggyPudding) { 135 ret = pudGet(p, key, false) 136 } else if (typeof p.get === 'function') { 137 ret = p.get(key) 138 } else { 139 ret = p[key] 140 } 141 return ret 142} 143 144const proxyHandler = { 145 has (obj, prop) { 146 return prop in obj.__specs && pudGet(obj, prop, false) !== undefined 147 }, 148 ownKeys (obj) { 149 return Object.keys(obj.__specs) 150 }, 151 get (obj, prop) { 152 if ( 153 typeof prop === 'symbol' || 154 prop.slice(0, 2) === '__' || 155 prop in FiggyPudding.prototype 156 ) { 157 return obj[prop] 158 } 159 return obj.get(prop) 160 }, 161 set (obj, prop, value) { 162 if ( 163 typeof prop === 'symbol' || 164 prop.slice(0, 2) === '__' 165 ) { 166 obj[prop] = value 167 return true 168 } else { 169 throw new Error('figgyPudding options cannot be modified. Use .concat() instead.') 170 } 171 }, 172 deleteProperty () { 173 throw new Error('figgyPudding options cannot be deleted. Use .concat() and shadow them instead.') 174 } 175} 176 177module.exports = figgyPudding 178function figgyPudding (specs, opts) { 179 function factory (...providers) { 180 return new Proxy(new FiggyPudding( 181 specs, 182 opts, 183 providers 184 ), proxyHandler) 185 } 186 return factory 187} 188 189function reverse (arr) { 190 const ret = [] 191 arr.forEach(x => ret.unshift(x)) 192 return ret 193} 194 195function entries (obj) { 196 return Object.keys(obj).map(k => [k, obj[k]]) 197} 198