You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							198 lines
						
					
					
						
							4.6 KiB
						
					
					
				
			
		
		
	
	
							198 lines
						
					
					
						
							4.6 KiB
						
					
					
				| '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) : 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
 | |
| }
 | |
| 
 | |
| function entries (obj) {
 | |
|   return Object.keys(obj).map(k => [k, obj[k]])
 | |
| }
 |