homebridge-lib 6.3.7 → 6.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,886 +0,0 @@
1
- // homebridge-lib/lib/OptionParser.js
2
- //
3
- // Library for Homebridge plugins.
4
- // Copyright © 2018-2023 Erik Baauw. All rights reserved.
5
-
6
- 'use strict'
7
-
8
- const events = require('events')
9
- const path = require('path')
10
- const net = require('net')
11
-
12
- const noop = () => {}
13
-
14
- /** User input error.
15
- * @hideconstructor
16
- * @extends Error
17
- * @memberof OptionParser
18
- */
19
- class UserInputError extends Error {}
20
-
21
- // Create a new RangeError or UserInputError, depending on userInput.
22
- function newRangeError (message, userInput = false) {
23
- return userInput ? new UserInputError(message) : new RangeError(message)
24
- }
25
-
26
- // Create a new SyntaxError or UserInputError, depending on userInput.
27
- function newSyntaxError (message, userInput = false) {
28
- return userInput ? new UserInputError(message) : new SyntaxError(message)
29
- }
30
-
31
- // Create a new TypeError or UserInputError, depending on userInput.
32
- function newTypeError (message, userInput = false) {
33
- return userInput ? new UserInputError(message) : new TypeError(message)
34
- }
35
-
36
- /** Parser and validator for options and other parameters.
37
- *
38
- * @extends EventEmitter
39
- * @emits userInputError
40
- * @emits warning
41
- */
42
- class OptionParser extends events.EventEmitter {
43
- /** Commonly used regular expressions.
44
- * @type {object}
45
- * @property {RegExp} hostname - Internet hostname.
46
- * @property {RegExp} int - Decimal integer.
47
- * @property {RegExp} intBin - Binary integer, optionally prefixed with `0b`.
48
- * @property {RegExp} intOct - Octal integer, optionally prefixed with `0o`.
49
- * @property {RegExp} intHex - Hexadecimal integer, optionally prefixed with `0x`.
50
- * @property {RegExp} ipv4 - IPv4 address in dot notation.
51
- * @property {RegExp} number - Number.
52
- * @property {RegExp} mac - Mac address (EUI-48).
53
- * @property {RegExp} mac64 - 64-bit mac address (EUI-64).
54
- * @property {RegExp} uuid - UUID.
55
- */
56
- static get patterns () {
57
- return Object.freeze({
58
- _host: /^(?:\[(.+)\]|([^:]+))(?::([0-9]{1,5}))?$/,
59
- hostname: /^[a-zA-Z0-9](:?[a-zA-Z0-9-]*[a-zA-Z0-9])*(:?\.[a-zA-Z0-9](:?[a-zA-Z0-9-]*[a-zA-Z0-9])*)*$/,
60
- int: /^\s*([+-]?)([0-9]+(?:\.0*)?)\s*$/,
61
- intBin: /^\s*([+-]?)(?:0[bB])([01]+)\s*$/,
62
- intOct: /^\s*([+-]?)(?:0[oO])([0-8]+)\s*$/,
63
- intHex: /^\s*([+-]?)(?:0[xX])([0-9A-Fa-f]+)\s*$/,
64
- ipv4: /^(\d{1,2}|[01]\d{2}|2[0-4]\d|25[0-5])\.(\d{1,2}|[01]\d{2}|2[0-4]\d|25[0-5])\.(\d{1,2}|[01]\d{2}|2[0-4]\d|25[0-5])\.(\d{1,2}|[01]\d{2}|2[0-4]\d|25[0-5])$/,
65
- number: /^\s*[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?\s*$/,
66
- mac: /^([0-9a-fA-F]{1,2})[:-]([0-9a-fA-F]{1,2})[:-]([0-9a-fA-F]{1,2})[:-]([0-9a-fA-F]{1,2})[:-]([0-9a-fA-F]{1,2})[:-]([0-9a-fA-F]{1,2})$/,
67
- mac64: /^([0-9a-fA-F]{1,2})[:-]([0-9a-fA-F]{1,2})[:-]([0-9a-fA-F]{1,2})[:-]([0-9a-fA-F]{1,2})[:-]([0-9a-fA-F]{1,2})[:-]([0-9a-fA-F]{1,2})[:-]([0-9a-fA-F]{1,2})[:-]([0-9a-fA-F]{1,2})$/,
68
- uuid: /^([0-9a-fA-F]{8})-([0-9a-fA-F]{4})-([1-5][0-9a-fA-F]{3})-([89abAB][0-9a-fA-F]{3})-([0-9a-fA-F]{12})$/
69
- })
70
- }
71
-
72
- static get UserInputError () { return UserInputError }
73
-
74
- /** Casts input value to boolean.
75
- *
76
- * Valid input values are:
77
- * - A boolean;
78
- * - A number with value 0 (false) or 1 (true);
79
- * - A string with value 'false', 'no', 'off', or '0' (false); or
80
- * with value 'true', 'yes', 'on', or '1' (true).
81
- * @param {!string} key - The key of the input value (for error messages).
82
- * @param {*} value - The input value.
83
- * @param {boolean} [userInput=false] - Value was input by user.
84
- * @returns {boolean} The value as boolean.
85
- * @throws {TypeError} On invalid input value.
86
- * @throws {UserError} On error, when value was input by user.
87
- */
88
- static toBool (key, value, userInput = false) {
89
- key === 'key' || key === 'nonEmpty' || OptionParser.toString('key', key, true)
90
- userInput === false || OptionParser.toBool('userInput', userInput)
91
-
92
- if (value == null) {
93
- throw newTypeError(`${key}: missing boolean value`, userInput)
94
- }
95
- if (typeof value === 'boolean') {
96
- return value
97
- }
98
- if (typeof value === 'string') {
99
- value = value.toLowerCase()
100
- }
101
- if (['true', 'yes', 'on', '1', 1].includes(value)) {
102
- return true
103
- }
104
- if (['false', 'no', 'off', '0', 0].includes(value)) {
105
- return false
106
- }
107
- throw newTypeError(`${key}: not a boolean`, userInput)
108
- }
109
-
110
- /** Casts input value to integer, optionally clamped between min and max.
111
- *
112
- * Valid input values are:
113
- * - A boolean: false (0) or true (1);
114
- * - A number with an integer value;
115
- * - A string holding an integer value in decimal, binary, octal or
116
- * hexadecimal notation.
117
- * @param {!string} key - The key of the input value (for error messages).
118
- * @param {*} value - The input value.
119
- * @param {?integer} min - Minimum value returned.
120
- * @param {?integer} max - Maximum value returned.
121
- * @param {boolean} [userInput=false] - Value was input by user.
122
- * @returns {integer} The value as integer.
123
- * @throws {TypeError} On invalid input value.
124
- * @throws {UserError} On error, when value was input by user.
125
- */
126
- static toInt (key, value, min = -Infinity, max = Infinity, userInput = false) {
127
- OptionParser.toString('key', key, true)
128
- min === -Infinity || OptionParser.toInt('min', min)
129
- max === Infinity || OptionParser.toInt('max', max)
130
- userInput === false || OptionParser.toBool('userInput', userInput)
131
- if (max < min) {
132
- throw newRangeError('max: smaller than min')
133
- }
134
-
135
- if (value == null) {
136
- throw newTypeError(`${key}: missing integer value`, userInput)
137
- }
138
- if (typeof value === 'boolean') {
139
- value = value ? 1 : 0
140
- } else if (typeof value === 'number' || typeof value === 'string') {
141
- if (OptionParser.patterns.int.test(value)) {
142
- value = parseInt(value)
143
- } else if (OptionParser.patterns.intHex.test(value)) {
144
- value = parseInt(value, 16)
145
- } else if (OptionParser.patterns.intOct.test(value)) {
146
- const a = OptionParser.patterns.intOct.exec(value)
147
- value = parseInt(a[1] + a[2], 8)
148
- } else if (OptionParser.patterns.intBin.test(value)) {
149
- const a = OptionParser.patterns.intBin.exec(value)
150
- value = parseInt(a[1] + a[2], 2)
151
- } else {
152
- throw newTypeError(`${key}: not an integer`, userInput)
153
- }
154
- } else {
155
- throw newTypeError(`${key}: not an integer`, userInput)
156
- }
157
- return Math.min(Math.max(value, min), max)
158
- }
159
-
160
- /** Converts an integer value to a formatted string.
161
- *
162
- * The integer value is converted to a string in the specified radix.
163
- * The string is prepended with spaces (radix 10) or zeroes (other radix
164
- * values) to match the minimum length.
165
- *
166
- * @param {!string} key - The key of the value (for error messages).
167
- * @param {integer} value - The input value.
168
- * @param {integer} [radix=10] - The radix.
169
- * @param {integer} [length=0] - The minimum length of the formatted string.
170
- * @param {boolean} [userInput=false] - Value was input by user.
171
- * @returns {string} The formatted string.
172
- * @throws {TypeError} On invalid input value.
173
- */
174
- static toIntString (key, value, radix = 10, length = 0, userInput = false) {
175
- radix === 10 || OptionParser.toInt('radix', 2, 36)
176
- length === 0 || OptionParser.toInt('length', 0, 32)
177
-
178
- if (value < 0 && radix !== 10) {
179
- throw new RangeError(`${key}: not an unsigned integer`, userInput)
180
- }
181
- value = OptionParser.toInt(
182
- 'value', value, undefined, undefined, userInput
183
- ).toString(radix).toUpperCase()
184
- if (value.length > length) {
185
- return value
186
- }
187
- const prefix = radix === 10
188
- ? ' '
189
- : '00000000000000000000000000000000'
190
- return (prefix + value).slice(-length)
191
- }
192
-
193
- /** Casts input value to number, optionally clamped between min and max.
194
- *
195
- * Valid input values are:
196
- * - A boolean: false (0) or true (1);
197
- * - A real number (not: NaN, -Infinity, Infinity);
198
- * - A string holding a number value.
199
- * @param {!string} key - The key of the input value (for error messages).
200
- * @param {*} value - The input value.
201
- * @param {?number} min - Minimum value returned.
202
- * @param {?number} max - Maximum value returned.
203
- * @param {boolean} [userInput=false] - Value was input by user.
204
- * @returns {number} The value as number.
205
- * @throws {TypeError} On invalid input value.
206
- * @throws {UserError} On error, when value was input by user.
207
- */
208
- static toNumber (key, value, min = -Infinity, max = Infinity, userInput = false) {
209
- OptionParser.toString('key', key, true)
210
- min === -Infinity || OptionParser.toNumber('min', min)
211
- max === Infinity || OptionParser.toNumber('max', max)
212
- userInput === false || OptionParser.toBool('userInput', userInput)
213
- if (max < min) {
214
- throw newRangeError('max: smaller than min')
215
- }
216
-
217
- if (value == null) {
218
- throw newTypeError(`${key}: missing number value`, userInput)
219
- }
220
- if (typeof value === 'boolean') {
221
- value = value ? 1 : 0
222
- } else if (typeof value === 'number' || typeof value === 'string') {
223
- if (OptionParser.patterns.number.test(value)) {
224
- value = parseFloat(value)
225
- } else {
226
- throw newTypeError(`${key}: not a number`, userInput)
227
- }
228
- } else {
229
- throw newTypeError(`${key}: not a number`, userInput)
230
- }
231
- return Math.min(Math.max(value, min), max)
232
- }
233
-
234
- /** Converts an integer value to a formatted string.
235
- *
236
- * The integer value is converted to a string, optionally with a fixed
237
- * number of decimals.
238
- * The string is prepended with `0`s to match the minimum length.
239
- *
240
- * @param {!string} key - The key of the value (for error messages).
241
- * @param {integer} value - The input value.
242
- * @param {integer} [length=0] - The minimum length of the formatted string.
243
- * @param {?integer} decimals - The fixed number of decimals
244
- * @param {boolean} [userInput=false] - Value was input by user.
245
- * @returns {string} The formatted string.
246
- * @throws {TypeError} On invalid input value.
247
- */
248
- static toNumberString (
249
- key, value, length = 0, decimals = null, userInput = false
250
- ) {
251
- OptionParser.toString('key', key, true)
252
- length === 0 || OptionParser.toInt('length', 0, 32)
253
- decimals === 0 || OptionParser.toInt('decimals', 0, 16)
254
-
255
- value = OptionParser.toNumber(key, value, undefined, undefined, userInput)
256
- if (decimals == null) {
257
- value = value.toString(10)
258
- } else {
259
- value = value.toFixed(decimals)
260
- }
261
- if (value.length > length) {
262
- return value
263
- }
264
- return (' ' + value).slice(-length)
265
- }
266
-
267
- /** Casts input value to string, optionally non-empty.
268
- *
269
- * Valid values are:
270
- * - A string.
271
- * - A boolean.
272
- * - A number.
273
- * @param {!string} key - The key of the value (for error messages).
274
- * @param {*} value - The input value.
275
- * @param {boolean} [nonEmpty=false] - Empty string is invalid value.
276
- * @param {boolean} [userInput=false] - Value was input by user.
277
- * @returns {string} The value as string.
278
- * @throws {TypeError} On invalid input value.
279
- * @throws {RangeError} On empty string, when noEmptyString has been set.
280
- * @throws {UserError} On error, when value was input by user.
281
- */
282
- static toString (key, value, nonEmpty = false, userInput = false) {
283
- key === 'key' || OptionParser.toString('key', key, true)
284
- nonEmpty === false || OptionParser.toBool('nonEmpty', nonEmpty)
285
- userInput === false || OptionParser.toBool('userInput', userInput)
286
-
287
- if (value == null && nonEmpty) {
288
- throw newTypeError(`${key}: missing string value`, userInput)
289
- } else if (value == null) {
290
- value = ''
291
- } else if (typeof value === 'boolean' || typeof value === 'number') {
292
- value = '' + value
293
- } else if (typeof value !== 'string') {
294
- throw newTypeError(`${key}: not a string`, userInput)
295
- }
296
- if (nonEmpty && value === '') {
297
- throw newRangeError(`${key}: not a non-empty string`, userInput)
298
- }
299
- return value
300
- }
301
-
302
- /** Casts input value to hostname[:port].
303
- * @param {!string} key - The key of the value (for error messages).
304
- * @param {*} value - The input value.
305
- * @param {boolean} [asString=false] - Return path as string instead of object.
306
- * @param {boolean} [userInput=false] - Value was input by user.
307
- * @returns {object|string} The value as { hostname: hostname, port: port }.
308
- * @throws {TypeError} On invalid input value.
309
- * @throws {RangeError} On empty string, when noEmptyString has been set.
310
- * @throws {UserError} On error, when value was input by user.
311
- */
312
- static toHost (key, value, asString = false, userInput = false) {
313
- key === 'key' || OptionParser.toString('key', key, true)
314
- asString === false || OptionParser.toBool('asString', asString)
315
- userInput === false || OptionParser.toBool('userInput', userInput)
316
-
317
- OptionParser.toString(key, value, true, userInput)
318
- const response = {}
319
- const list = OptionParser.patterns._host.exec(value)
320
- if (list == null) {
321
- throw newRangeError(`${key}: not a valid host`, userInput)
322
- }
323
- if (list[1] != null) {
324
- if (!net.isIPv6(list[1])) {
325
- throw newRangeError(`${key}: [${list[1]}]: not a valid IPv6 address`, userInput)
326
- }
327
- response.hostname = '[' + list[1] + ']'
328
- } else if (net.isIPv4(list[2])) {
329
- response.hostname = list[2].split('.').map((byte) => {
330
- return parseInt(byte)
331
- }).join('.')
332
- } else if (OptionParser.patterns.hostname.test(list[2])) {
333
- response.hostname = list[2]
334
- } else {
335
- throw newRangeError(`${key}: ${list[2]}: not a valid hostname or IPv4 address`, userInput)
336
- }
337
- let host = response.hostname
338
- if (list[3] != null) {
339
- const port = parseInt(list[3], 10)
340
- if (port < 0 || port > 65535) {
341
- throw newRangeError(`${key}: ${port}: not a valid port`, userInput)
342
- }
343
- response.port = port
344
- host += ':' + port
345
- }
346
- return asString ? host : response
347
- }
348
-
349
- /** Casts input value to path.
350
- *
351
- * @param {!string} key - The key of the value (for error messages).
352
- * @param {*} value - The input value.
353
- * @param {boolean} [userInput=false] - Value was input by user.
354
- * @returns {string} The value as normalised resource path.
355
- * @throws {TypeError} On invalid input value.
356
- * @throws {RangeError} On empty string, on string not starting with '/'.
357
- * @throws {UserError} On error, when value was input by user.
358
- */
359
- static toPath (key, value, userInput = false) {
360
- OptionParser.toString('key', key, true)
361
- userInput === false || OptionParser.toBool('userInput', userInput)
362
-
363
- value = OptionParser.toString(key, value, true, userInput)
364
- if (value[0] !== '/') {
365
- throw newRangeError(`${key}: ${value}: not a valid path`, key, value)
366
- }
367
- return path.posix.normalize(value)
368
- }
369
-
370
- /** Casts input value to array.
371
- *
372
- * Valid values are:
373
- * - Null (empty array);
374
- * - A boolean, number, or string (singleton array);
375
- * - An array.
376
- * @param {!string} key - The key of the value (for error messages).
377
- * @param {*} value - The input value.
378
- * @param {boolean} [userInput=false] - Value was input by user.
379
- * @returns {string} The value as array.
380
- * @throws {TypeError} On invalid input value.
381
- * @throws {UserError} On error, when value was input by user.
382
- */
383
- static toArray (key, value, userInput = false) {
384
- OptionParser.toString('key', key, true)
385
- userInput === false || OptionParser.toBool('userInput', userInput)
386
-
387
- if (value == null) {
388
- return []
389
- }
390
- if (['boolean', 'number', 'string'].includes(typeof value)) {
391
- return [value]
392
- }
393
- if (Array.isArray(value)) {
394
- return value
395
- }
396
- throw newTypeError(`${key}: not an array`, userInput)
397
- }
398
-
399
- /** Casts input value to object.
400
- *
401
- * Valid values are:
402
- * - Null (empty object);
403
- * - A proper object (i.e. not a class instance).
404
- * @param {!string} key - The key of the value (for error messages).
405
- * @param {*} value - The input value.
406
- * @param {boolean} [userInput=false] - Value was input by user.
407
- * @returns {Object} The value.
408
- * @throws {TypeError} On invalid input value.
409
- * @throws {UserError} On error, when value was input by user.
410
- */
411
- static toObject (key, value, userInput = false) {
412
- OptionParser.toString('key', key, true)
413
- userInput === false || OptionParser.toBool('userInput', userInput)
414
-
415
- if (value == null) {
416
- return {}
417
- }
418
- if (
419
- typeof value !== 'object' || value == null ||
420
- value.constructor.name !== 'Object'
421
- ) {
422
- throw newTypeError(`${key}: not an object`, userInput)
423
- }
424
- return value
425
- }
426
-
427
- /** Casts input value to function.
428
- *
429
- * Valid values are:
430
- * - A proper function (i.e. not a class).
431
- * @param {!string} key - The key of the value (for error messages).
432
- * @param {*} value - The input value.
433
- * @returns {function} The value.
434
- * @throws {TypeError} On invalid input value.
435
- */
436
- static toFunction (key, value) {
437
- OptionParser.toString('key', key, true)
438
-
439
- if (value == null) {
440
- throw new TypeError(`${key}: missing function value`)
441
- }
442
- if (
443
- typeof value === 'function' && value.prototype == null &&
444
- value.constructor.name === 'Function'
445
- ) {
446
- return value
447
- }
448
- throw new TypeError(`${key}: not a function`)
449
- }
450
-
451
- /** Casts input value to function.
452
- *
453
- * Valid values are:
454
- * - A proper async function.
455
- * @param {!string} key - The key of the value (for error messages).
456
- * @param {*} value - The input value.
457
- * @returns {function} The value.
458
- * @throws {TypeError} On invalid input value.
459
- */
460
- static toAsyncFunction (key, value) {
461
- OptionParser.toString('key', key, true)
462
-
463
- if (value == null) {
464
- throw new TypeError(`${key}: missing async function value`)
465
- }
466
- if (
467
- typeof value === 'function' &&
468
- value.constructor.name === 'AsyncFunction'
469
- ) {
470
- return value
471
- }
472
- throw new TypeError(`${key}: not an async function`)
473
- }
474
-
475
- /** Casts input value to class.
476
- *
477
- * Valid values are:
478
- * - A proper Class or function with a prototype.
479
- * @param {!string} key - The key of the value (for error messages).
480
- * @param {*} value - The input value.
481
- * @param {?Class} SuperClass - Check for subclass of SuperClass.
482
- * @returns {*} The value.
483
- * @throws {TypeError} On invalid input value.
484
- */
485
- static toClass (key, value, SuperClass) {
486
- OptionParser.toString('key', key, true)
487
- SuperClass === undefined || OptionParser.toClass('SuperClass', SuperClass)
488
-
489
- if (value == null) {
490
- throw new TypeError(`${key}: missing class value`)
491
- }
492
- if (typeof value !== 'function' || value.prototype == null) {
493
- throw new TypeError(`${key}: not a class`)
494
- }
495
- if (
496
- SuperClass != null && value !== SuperClass &&
497
- !(value.prototype instanceof SuperClass)
498
- ) {
499
- throw new TypeError(`${key}: not a subclass of ${SuperClass.name}`)
500
- }
501
- return value
502
- }
503
-
504
- /** Casts input value to class instance.
505
- *
506
- * Valid values are:
507
- * - A class instance or a proper function.
508
- * @param {!string} key - The key of the value (for error messages).
509
- * @param {*} value - The input value.
510
- * @param {!Class} Class - Check for instance of Class.
511
- * @returns {Class} The value.
512
- * @throws {TypeError} On invalid input value.
513
- */
514
- static toInstance (key, value, Class) {
515
- OptionParser.toString('key', key, true)
516
- OptionParser.toClass('Class', Class)
517
-
518
- if (value == null) {
519
- throw new TypeError(`${key}: missing instance of ${Class.name} value`)
520
- }
521
- if (value instanceof Class) {
522
- return value
523
- }
524
- throw new TypeError(`${key}: not an instance of ${Class.name}`)
525
- }
526
-
527
- /** Creates a new OptionParser instance
528
- *
529
- * @param {boolean} [userInput=false] - Options were input by user.
530
- */
531
- constructor (object = {}, userInput = false) {
532
- super()
533
- this._object = OptionParser.toObject('object', object)
534
- this._userInput = OptionParser.toBool('useerInput', userInput)
535
- this._callbacks = {}
536
- }
537
-
538
- /** Checks that key is valid and not yet in use.
539
- *
540
- * @param {!string} key - The key.
541
- * @throws {TypeError} When key is not a string.
542
- * @throws {RangeError} When key is empty string.
543
- * @throws {SyntaxError} On duplicate key.
544
- */
545
- _toKey (key) {
546
- key = OptionParser.toString('key', key, true)
547
- if (this._callbacks[key] != null) {
548
- throw new SyntaxError(`${key}: duplicate key`)
549
- }
550
- return key
551
- }
552
-
553
- /** Defines a key that takes an array as value.
554
- *
555
- * @param {!string} key - The key.
556
- * @return {OptionParser} this - For chaining.
557
- * @throws {TypeError} When key is not a string.
558
- * @throws {RangeError} When key is empty string.
559
- * @throws {SyntaxError} On duplicate key.
560
- */
561
- arrayKey (key) {
562
- key = this._toKey(key)
563
-
564
- this._callbacks[key] = (value) => {
565
- this._object[key] = OptionParser.toArray(key, value, this._userInput)
566
- }
567
- return this
568
- }
569
-
570
- /** Defines a key that takes an async function as value.
571
- *
572
- * @param {!string} key - The key.
573
- * @return {OptionParser} this - For chaining.
574
- * @throws {TypeError} When key is not a string.
575
- * @throws {RangeError} When key is empty string.
576
- * @throws {SyntaxError} On duplicate key.
577
- */
578
- asyncFunctionKey (key) {
579
- key = this._toKey(key)
580
-
581
- this._callbacks[key] = (value) => {
582
- this._object[key] = OptionParser.toAsyncFunction(key, value)
583
- }
584
- return this
585
- }
586
-
587
- /** Defines a key that takes a boolean value.
588
- *
589
- * @param {!string} key - The key.
590
- * @return {OptionParser} this - For chaining.
591
- * @throws {TypeError} When key is not a string.
592
- * @throws {RangeError} When key is empty string.
593
- * @throws {SyntaxError} On duplicate key.
594
- */
595
- boolKey (key) {
596
- key = this._toKey(key)
597
-
598
- this._callbacks[key] = (value) => {
599
- this._object[key] = OptionParser.toBool(key, value, this._userInput)
600
- }
601
- return this
602
- }
603
-
604
- /** Defines a key that takes an enum value.
605
- *
606
- * @param {!string} key - The key.
607
- * @return {OptionParser} this - For chaining.
608
- * @throws {TypeError} When key is not a string.
609
- * @throws {RangeError} When key is empty string.
610
- * @throws {SyntaxError} On duplicate key.
611
- */
612
- enumKey (key) {
613
- key = this._toKey(key)
614
-
615
- this._callbacks[key] = (value) => {
616
- value = OptionParser.toString(
617
- key, value, true, this._userInput
618
- )
619
- const callback = this._callbacks[key].list[value]
620
- if (callback == null) {
621
- throw newRangeError(`${value}: invalid ${key}`, this._userInput)
622
- }
623
- this._object[key] = value
624
- callback()
625
- }
626
- this._callbacks[key].list = {}
627
- return this
628
- }
629
-
630
- /** Defines a value for an enum key.
631
- *
632
- * @param {!string} key - The key.
633
- * @param {!string} value - The key.
634
- * @param {?function} callback - Function to call when enum value is present.
635
- * @return {OptionParser} this - For chaining.
636
- * @throws {TypeError} When key is not a string.
637
- * @throws {RangeError} When key is empty string.
638
- * @throws {SyntaxError} On duplicate key.
639
- */
640
- enumKeyValue (key, value, callback = noop) {
641
- key = OptionParser.toString('key', key, true)
642
- value = OptionParser.toString('value', value, true)
643
- OptionParser.toFunction(key, this._callbacks[key])
644
- callback = OptionParser.toFunction('callback', callback)
645
-
646
- this._callbacks[key].list[value] = callback
647
- return this
648
- }
649
-
650
- /** Defines a key that takes a function as value.
651
- *
652
- * @param {!string} key - The key.
653
- * @return {OptionParser} this - For chaining.
654
- * @throws {TypeError} When key is not a string.
655
- * @throws {RangeError} When key is empty string.
656
- * @throws {SyntaxError} On duplicate key.
657
- */
658
- functionKey (key) {
659
- key = this._toKey(key)
660
-
661
- this._callbacks[key] = (value) => {
662
- this._object[key] = OptionParser.toFunction(key, value)
663
- }
664
- return this
665
- }
666
-
667
- /** Defines a key that takes a hostname[:port] as value.
668
- *
669
- * @param {!string} key - The key.
670
- * @param {string} [hostnameKey=hostname] - The key for the hostname.
671
- * @param {string} [portKey=port] - The key for the port.
672
- * @return {OptionParser} this - For chaining.
673
- * @throws {TypeError} When key is not a string.
674
- * @throws {RangeError} When key is empty string.
675
- * @throws {SyntaxError} On duplicate key.
676
- */
677
- hostKey (key = 'host', hostnameKey = 'hostname', portKey = 'port') {
678
- key = this._toKey(key)
679
- hostnameKey = OptionParser.toString('hostnameKey', hostnameKey, true)
680
- portKey = OptionParser.toString('portKey', portKey, true)
681
-
682
- this._callbacks[key] = (value) => {
683
- const host = OptionParser.toHost(key, value, false, this._userInput)
684
- this._object[hostnameKey] = host.hostname
685
- if (host.port != null) {
686
- this._object[portKey] = host.port
687
- }
688
- }
689
- return this
690
- }
691
-
692
- /** Defines a key that takes an integer value,
693
- * optionally clamped between min and max.
694
- *
695
- * @param {!string} key - The key.
696
- * @param {!Class} Class - Check for instance of Class.
697
- * @return {OptionParser} this - For chaining.
698
- * @throws {TypeError} When key is not a string.
699
- * @throws {RangeError} When key is empty string.
700
- * @throws {SyntaxError} On duplicate key.
701
- */
702
- instanceKey (key, Class) {
703
- key = this._toKey(key)
704
- Class = OptionParser.toClass('Class', Class)
705
-
706
- this._callbacks[key] = (value) => {
707
- this._object[key] = OptionParser.toInstance(key, value, Class)
708
- }
709
- return this
710
- }
711
-
712
- /** Defines a key that takes an integer value,
713
- * optionally clamped between min and max.
714
- *
715
- * @param {!string} key - The key.
716
- * @param {?integer} min - Minimum value returned.
717
- * @param {?integer} max - Maximum value returned.
718
- * @return {OptionParser} this - For chaining.
719
- * @throws {TypeError} When key is not a string.
720
- * @throws {RangeError} When key is empty string.
721
- * @throws {SyntaxError} On duplicate key.
722
- */
723
- intKey (key, min, max) {
724
- key = this._toKey(key)
725
- min = min == null ? -Infinity : OptionParser.toInt('min', min)
726
- max = max == null ? Infinity : OptionParser.toInt('max', max)
727
- if (max < min) {
728
- throw newRangeError('max: smaller than min')
729
- }
730
-
731
- this._callbacks[key] = (value) => {
732
- this._object[key] = OptionParser.toInt(key, value, min, max, this._userInput)
733
- }
734
- return this
735
- }
736
-
737
- /** Defines a key that takes a list of strings as value.
738
- *
739
- * @param {!string} key - The key.
740
- * @return {OptionParser} this - For chaining.
741
- * @throws {TypeError} When key is not a string.
742
- * @throws {RangeError} When key is empty string.
743
- * @throws {SyntaxError} On duplicate key.
744
- */
745
- listKey (key) {
746
- key = this._toKey(key)
747
-
748
- this._callbacks[key] = (value) => {
749
- const array = []
750
- const map = {}
751
- for (const element of OptionParser.toArray(key, value)) {
752
- try {
753
- OptionParser.toString(`${key}.${element}`, element, true, this._userInput)
754
- if (map[element]) {
755
- throw newSyntaxError(`${key}.${element}: duplicate key`, this._userInput)
756
- }
757
- map[element] = true
758
- array.push(element)
759
- } catch (error) {
760
- if (error instanceof UserInputError) {
761
- this.emit('userInputError', `${key}: ${error.message}`)
762
- } else {
763
- throw error
764
- }
765
- }
766
- }
767
- this._object[key] = array
768
- }
769
- return this
770
- }
771
-
772
- /** Defines a key that takes an number value,
773
- * optionally clamped between min and max.
774
- *
775
- * @param {!string} key - The key.
776
- * @param {?number} min - Minimum value returned.
777
- * @param {?number} max - Maximum value returned.
778
- * @return {OptionParser} this - For chaining.
779
- * @throws {TypeError} When key is not a string.
780
- * @throws {RangeError} When key is empty string.
781
- * @throws {SyntaxError} On duplicate key.
782
- */
783
- numberKey (key, min, max) {
784
- key = this._toKey(key)
785
- min = min == null ? -Infinity : OptionParser.toNumber('min', min)
786
- max = max == null ? Infinity : OptionParser.toNumber('max', max)
787
- if (max < min) {
788
- throw newRangeError('max: smaller than min')
789
- }
790
-
791
- this._callbacks[key] = (value) => {
792
- this._object[key] = OptionParser.toNumber(key, value, min, max, this._userInput)
793
- }
794
- return this
795
- }
796
-
797
- /** Defines a key that takes an object as value.
798
- *
799
- * @param {!string} key - The key.
800
- * @return {OptionParser} this - For chaining.
801
- * @throws {TypeError} When key is not a string.
802
- * @throws {RangeError} When key is empty string.
803
- * @throws {SyntaxError} On duplicate key.
804
- */
805
- objectKey (key) {
806
- key = this._toKey(key)
807
-
808
- this._callbacks[key] = (value) => {
809
- this._object[key] = OptionParser.toObject(key, value, this._userInput)
810
- }
811
- return this
812
- }
813
-
814
- /** Defines a key that takes a resource path as value.
815
- *
816
- * @param {!string} key - The key.
817
- * @return {OptionParser} this - For chaining.
818
- * @throws {TypeError} When key is not a string.
819
- * @throws {RangeError} When key is empty string.
820
- * @throws {SyntaxError} On duplicate key.
821
- */
822
- pathKey (key) {
823
- key = this._toKey(key)
824
-
825
- this._callbacks[key] = (value) => {
826
- this._object[key] = OptionParser.toPath(key, value, this._userInput)
827
- }
828
- return this
829
- }
830
-
831
- /** Defines a key that takes a string value.
832
- *
833
- * @param {!string} key - The key.
834
- * @param {boolean} [nonEmpty=false] - Reject empty string.
835
- * @return {OptionParser} this - For chaining.
836
- * @throws {TypeError} When key is not a string.
837
- * @throws {RangeError} When key is empty string.
838
- * @throws {SyntaxError} On duplicate key.
839
- */
840
- stringKey (key, nonEmpty = false) {
841
- key = this._toKey(key)
842
-
843
- this._callbacks[key] = (value) => {
844
- this._object[key] = OptionParser.toString(
845
- key, value, nonEmpty, this._userInput
846
- )
847
- }
848
- return this
849
- }
850
-
851
- /** Parse options.
852
- *
853
- * @param {object} options - The input options.
854
- * @param {?object} defaults - The default values, to be overwritten by
855
- * the corresponding values in `options`.
856
- * @returns {object} The
857
- * @throws {TypeError} When option has wrong type.
858
- * @throws {RangeError} When option has wrong value.
859
- * @throws {SyntaxError} Unknown option.
860
- * @throws {UserInputError} On error, when value was input by user.
861
- */
862
- parse (options) {
863
- options = OptionParser.toObject('options', options)
864
-
865
- for (const key in options) {
866
- try {
867
- const value = options[key]
868
- if (this._callbacks[key] == null) {
869
- throw newSyntaxError(`${key}: invalid key`, this._userInput)
870
- }
871
- this._callbacks[key](value)
872
- } catch (error) {
873
- if (error instanceof UserInputError) {
874
- // this.emit('userInputError', `${key}: ${error.message}`)
875
- this.emit('userInputError', error)
876
- } else {
877
- // error.message = `${key}: ${error.message}`
878
- throw error
879
- }
880
- }
881
- }
882
- return this._object
883
- }
884
- }
885
-
886
- module.exports = OptionParser