-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
190 lines (161 loc) · 6.25 KB
/
index.js
File metadata and controls
190 lines (161 loc) · 6.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
"use strict";
(function(root) {
/**
* Enum builder - wraps arrays and maps in a function that
* supplies (precomputed) key and value lists, maps, and
* reverse maps. Supplies multiple means of lookup and
* reference, and offers full control over how enum values
* are generated.
*
* @author Evan King
* @see https://github.com/evan-king/node-primitive-enum
*
* Usage:
* const myEnum = Enum({a: 'x', b: 'y'});
* myEnum.keys; // ['a', 'b']
* myEnum.values; // ['x', 'y']
* myEnum.map; // {a: 'x', b: 'y'}
* myEnum.reverseMap; // {x: 'a', y: 'b'}
* myEnum('a'); // 'x'
* myEnum('x'): // 'a'
* myEnum.a; // 'x'
* myEnum.x; // 'a'
* myEnum.count: // 2
*
* @param mixed inputMap Array of keys or Object mapping keys to values
* @param mixed options Configuration object supporting these options:
* - transform: Function describing how to generate enum values from inputMap
* - defaultKey: String specifying which key/value are considered the default option
* Alternatively, options can be either one of those directly.
*/
const Enum = function PrimitiveEnumBuilder(inputMap, options) {
const
opts = buildOptions(options),
keys = [],
values = [],
map = {},
reverseMap = {},
API = function PrimitiveEnum(lookup) { return API[''+lookup]; } ;
function buildOptions(options) {
switch(typeof options) {
case 'string': return {defaultKey: options};
case 'function': return {transform: options};
case 'object': return options;
case 'undefined': return {};
default: throw new TypeError('Invalid argument:' + typeof options);
}
}
// Note: We don't want a value appearing twice on one side or
// appearing on both sides unless as part of the same mapping.
// We could handle them safely with slightly reduced functionality,
// but that's a bad enum anyway.
function checkAvailable() {
const args = Array.prototype.slice.call(arguments);
args.forEach(function(lookup) {
if(API[''+lookup] === undefined) return;
throw new Error('Enum must be bijective with keys distinctive from values');
});
}
function prop(name, value, enumer) {
Object.defineProperty(API, name, {
enumerable: enumer || false,
writable: false,
configurable: false,
value: value,
});
}
function addMapping(key, val) {
key.__proto__ = API;
val.__proto__ = API;
checkAvailable(key, val);
keys.push(key);
values.push(val);
map[''+key] = val;
reverseMap[''+val] = key;
prop(key, val, true);
if(''+key !== ''+val) prop(val, key);
}
let transform = opts.transform;
if(Array.isArray(inputMap)) {
if(transform === undefined) transform = Enum.defaultArrayTransform || Enum.identity;
if(typeof transform !== 'function') throw new Error('Invalid transform');
inputMap.forEach((key, idx) => addMapping(key, transform(key, idx)));
} else {
if(transform === undefined) transform = Enum.defaultObjectTransform || Enum.identity;
if(typeof transform !== 'function') throw new Error('Invalid transform');
Object.keys(inputMap).forEach(key => addMapping(key, transform(inputMap[key], key)));
}
const defIdx = (opts.defaultKey === undefined) ? 0 : keys.indexOf(opts.defaultKey);
if(defIdx < 0) throw new Error('Invalid default key');
function toJSON() {
return { type: 'PrimitiveEnum', map: map, defaultKey: keys[defIdx] };
}
// Provide a string representation for comparability
const asString = '[Function: PrimitiveEnum] '+keys.join(',')+'|'+values.join(',')+'|'+defIdx;
API.__proto__ = Enum.prototype;
prop('map', Object.freeze(map));
prop('reverseMap', Object.freeze(reverseMap));
prop('keys', Object.freeze(keys));
prop('values', Object.freeze(values));
prop('count', keys.length);
prop('key', val => reverseMap[''+val]);
prop('value', key => map[''+key]);
prop('defaultKey', keys[defIdx]);
prop('defaultValue', values[defIdx]);
prop('toJSON', toJSON);
prop('toString', () => asString);
return Object.freeze(API);
}
function eprop(name, value, write) {
Object.defineProperty(Enum, name, {
enumerable: false,
writable: write,
configurable: false,
value: value,
});
}
eprop('fromJSON', function(obj) {
if(typeof obj === "string") obj = JSON.parse(obj);
if(obj.type != 'PrimitiveEnum') {
throw new TypeError('Input is not a serialized PrimitiveEnum');
}
return Enum(obj.map, obj.defaultKey);
});
// Pre-built mapping transforms
// key == value
eprop('identity', key => key);
// Note: never use 0 as an enum value (because it is falsey)
eprop('sequence', (key, idx) => idx + 1);
// For arrays, create values that can be combined as bitwise flags
eprop('bitwise', (key, idx) => Math.pow(2, idx));
// CONSTANT_KEY => constant-value
eprop('idString', key => key.toLowerCase().replace(/( |_)+/g, '-'));
// Configurable defaults
eprop('defaultArrayTransform', Enum.sequence, true);
eprop('defaultObjectTransform', undefined, true);
// Fallback default transform for arrays and objects. Changing not recommended - may be hard-coded before 1.0.0
eprop('defaultTransform', Enum.identity, true);
// Undocumented method for unit-testing convenience
eprop('resetDefaultTransforms', function() {
Enum.defaultArrayTransform = Enum.sequence;
Enum.defaultObjectTransform = undefined;
Enum.defaultTransform = Enum.identity;
});
// Determine how to publish the library
if(typeof module === 'object' && typeof module.exports === 'object') {
// Publish CommonJS module
module.exports = Enum;
} else {
// Globally expose PrimitiveEnum, allowing rollback via PrimtiveEnum.noConflict()
var origModule = root.PrimitiveEnum;
root.PrimitiveEnum = Enum;
eprop('noConflict', function() {
root.PrimitiveEnum = origModule;
return Enum;
});
if(typeof define === 'function' && define.amd) {
// Also expose as AMD
define('PrimitiveEnum', [], () => Enum);
}
}
})(this);