• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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