| 'use strict'; |
| |
| var normalize = require('value-or-function'); |
| |
| var slice = Array.prototype.slice; |
| |
| function createResolver(config, options) { |
| // TODO: should the config object be validated? |
| config = config || {}; |
| options = options || {}; |
| |
| var resolver = { |
| resolve: resolve, |
| }; |
| |
| |
| // Keep constants separately |
| var constants = {}; |
| |
| function resolveConstant(key) { |
| if (constants.hasOwnProperty(key)) { |
| return constants[key]; |
| } |
| |
| var definition = config[key]; |
| // Ignore options that are not defined |
| if (!definition) { |
| return; |
| } |
| |
| var option = options[key]; |
| |
| if (option != null) { |
| if (typeof option === 'function') { |
| return; |
| } |
| option = normalize.call(resolver, definition.type, option); |
| if (option != null) { |
| constants[key] = option; |
| return option; |
| } |
| } |
| |
| var fallback = definition.default; |
| if (option == null && typeof fallback !== 'function') { |
| constants[key] = fallback; |
| return fallback; |
| } |
| } |
| |
| |
| // Keep requested keys to detect (and disallow) recursive resolution |
| var stack = []; |
| |
| function resolve(key) { |
| var option = resolveConstant(key); |
| if (option != null) { |
| return option; |
| } |
| |
| var definition = config[key]; |
| // Ignore options that are not defined |
| if (!definition) { |
| return; |
| } |
| |
| if (stack.indexOf(key) >= 0) { |
| throw new Error('Recursive resolution denied.'); |
| } |
| |
| option = options[key]; |
| var fallback = definition.default; |
| var appliedArgs = slice.call(arguments, 1); |
| var args = [definition.type, option].concat(appliedArgs); |
| |
| function toResolve() { |
| stack.push(key); |
| var option = normalize.apply(resolver, args); |
| |
| if (option == null) { |
| option = fallback; |
| if (typeof option === 'function') { |
| option = option.apply(resolver, appliedArgs); |
| } |
| } |
| |
| return option; |
| } |
| |
| function onResolve() { |
| stack.pop(); |
| } |
| |
| return tryResolve(toResolve, onResolve); |
| } |
| |
| |
| return resolver; |
| } |
| |
| |
| function tryResolve(toResolve, onResolve) { |
| try { |
| return toResolve(); |
| } finally { |
| onResolve(); |
| } |
| } |
| |
| |
| module.exports = createResolver; |