blob: 21ef429d8aac2d32227b5d6410bc34f8e2563c6b [file] [log] [blame]
Leo Repp58b9f112021-11-22 11:57:47 +01001'use strict';
2
3var normalize = require('value-or-function');
4
5var slice = Array.prototype.slice;
6
7function createResolver(config, options) {
8 // TODO: should the config object be validated?
9 config = config || {};
10 options = options || {};
11
12 var resolver = {
13 resolve: resolve,
14 };
15
16
17 // Keep constants separately
18 var constants = {};
19
20 function resolveConstant(key) {
21 if (constants.hasOwnProperty(key)) {
22 return constants[key];
23 }
24
25 var definition = config[key];
26 // Ignore options that are not defined
27 if (!definition) {
28 return;
29 }
30
31 var option = options[key];
32
33 if (option != null) {
34 if (typeof option === 'function') {
35 return;
36 }
37 option = normalize.call(resolver, definition.type, option);
38 if (option != null) {
39 constants[key] = option;
40 return option;
41 }
42 }
43
44 var fallback = definition.default;
45 if (option == null && typeof fallback !== 'function') {
46 constants[key] = fallback;
47 return fallback;
48 }
49 }
50
51
52 // Keep requested keys to detect (and disallow) recursive resolution
53 var stack = [];
54
55 function resolve(key) {
56 var option = resolveConstant(key);
57 if (option != null) {
58 return option;
59 }
60
61 var definition = config[key];
62 // Ignore options that are not defined
63 if (!definition) {
64 return;
65 }
66
67 if (stack.indexOf(key) >= 0) {
68 throw new Error('Recursive resolution denied.');
69 }
70
71 option = options[key];
72 var fallback = definition.default;
73 var appliedArgs = slice.call(arguments, 1);
74 var args = [definition.type, option].concat(appliedArgs);
75
76 function toResolve() {
77 stack.push(key);
78 var option = normalize.apply(resolver, args);
79
80 if (option == null) {
81 option = fallback;
82 if (typeof option === 'function') {
83 option = option.apply(resolver, appliedArgs);
84 }
85 }
86
87 return option;
88 }
89
90 function onResolve() {
91 stack.pop();
92 }
93
94 return tryResolve(toResolve, onResolve);
95 }
96
97
98 return resolver;
99}
100
101
102function tryResolve(toResolve, onResolve) {
103 try {
104 return toResolve();
105 } finally {
106 onResolve();
107 }
108}
109
110
111module.exports = createResolver;