@lumjs/core 1.26.0 → 1.30.0

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.
package/lib/opt.js DELETED
@@ -1,664 +0,0 @@
1
- /**
2
- * Functions for working with options and default values.
3
- * @module @lumjs/core/opt
4
- */
5
-
6
- const
7
- {
8
- U,F,S,N,B,
9
- isObj,isComplex,isArray,isNil,needObj,needType
10
- } = require('./types');
11
- const {insert} = require('./arrays/add');
12
- const {getObjectPath} = require('./obj/ns');
13
-
14
- // Aliases for Opts#get() and Opts#find()
15
- const OPTS_ALIASES =
16
- {
17
- 'null': 'allowNull',
18
- 'lazy': 'isLazy',
19
- }
20
-
21
- /**
22
- * A helper to support both positional arguments and named
23
- * options in the same method signature.
24
- *
25
- * @param {object} opts - Options built from positional arguments
26
- *
27
- * Keep in mind that this object **WILL** be modified!
28
- *
29
- * @param {string} optArg - The option that may contain named options
30
- *
31
- * Generally the name of the first positional argument that may be
32
- * an `object` full of options or a different positional argument value.
33
- *
34
- * The biggest limitation is that it cannot be an `object` value when used
35
- * as a positional argument, as that will always be seen as the _options_.
36
- *
37
- * @param {*} optDef - A default value for `opts[optArg]`
38
- *
39
- * If `opts[optArg]` was an `object`, we'll compose its properties
40
- * into `opts` directly. If after that `opts[optArg]` is still the
41
- * options `object` then this value will be used instead.
42
- *
43
- * @param {boolean} [validate=true] Ensure `opts` is an object?
44
- *
45
- * Should only be disabled if you know for certain it is.
46
- *
47
- * @example <caption>Example usage</caption>
48
- *
49
- * function example(first=true, second=null, third="test")
50
- * {
51
- * const opts = argOpts({first, second, third}, 'first', true);
52
- * }
53
- *
54
- * @alias module:@lumjs/core/opt.argOpts
55
- */
56
- function argOpts(opts, optArg, optDef, validate=true)
57
- {
58
- if (validate) needObj(opts, false, 'invalid opts object');
59
-
60
- if (isObj(opts[optArg]))
61
- { // Merge the named options.
62
- const specOpts = opts[optArg];
63
- Object.assign(opts, specOpts);
64
- if (opts[optArg] === specOpts)
65
- { // specOpts didn't override the real option.
66
- opts[optArg] = optDef;
67
- }
68
- }
69
-
70
- } // argOpts()
71
-
72
- exports.argOpts = argOpts;
73
-
74
- // Private helper for `val()` and `get()` to support new-style options.
75
- function _opts(opts, defNull)
76
- {
77
- return argOpts(opts, 'allowNull', defNull, false);
78
- }
79
-
80
- /**
81
- * See if a value is *set*, and if not, return a default value.
82
- *
83
- * This function used to use all positional arguments, but it now
84
- * supports named options if an `object` is passed as the third argument.
85
- * If both named options and the corresponding positional arguments are
86
- * specified, the named options will take precedence.
87
- *
88
- * @param {*} optvalue - The value we are testing.
89
- * @param {*} defvalue - The default value if opt was null or undefined.
90
- *
91
- * @param {(object|boolean)} opts - Options
92
- *
93
- * If this is a `boolean` it is used as the `allowNull` option.
94
- *
95
- * @param {boolean} [opts.allowNull=false] If true, allow null to count as *set*.
96
- * @param {boolean} [opts.isLazy=false] If true, and `defvalue` is a function,
97
- * use the value from the function as
98
- * the default.
99
- * @param {object} [opts.lazyThis=null] If `isLazy` is true, this object will
100
- * be used as `this` for the function.
101
- * @param {Array} [opts.lazyArgs] If `isLazy` is true, this may be used
102
- * as a list of arguments to pass.
103
- *
104
- * @param {boolean} [isLazy=false] Same as `opts.isLazy`
105
- * @param {object} [lazyThis=null] Same as `opts.lazyThis`
106
- * @param {Array} [lazyArgs] Same as `opts.lazyArgs`
107
- *
108
- * @return {*} Either `optvalue` or `defvalue` depending on the test.
109
- * @alias module:@lumjs/core/opt.val
110
- */
111
- function val(optvalue, defvalue,
112
- allowNull=false,
113
- isLazy=false,
114
- lazyThis=null,
115
- lazyArgs=[])
116
- {
117
- const opts = _opts({allowNull,isLazy,lazyThis,lazyArgs}, false);
118
-
119
- if (typeof optvalue === U || (!opts.allowNull && optvalue === null))
120
- { // The defined value was not "set" as per our rules.
121
- if (opts.isLazy && typeof defvalue === F)
122
- { // Get the default value from a passed in function.
123
- return defvalue.apply(opts.lazyThis, opts.lazyArgs);
124
- }
125
- return defvalue;
126
- }
127
-
128
- return optvalue;
129
- }
130
-
131
- exports.val = val;
132
-
133
- /**
134
- * See if a property in an object is set.
135
- *
136
- * If it is, return the property, otherwise return a default value.
137
- * This uses the `val()` method, and as such supports the same arguments.
138
- * However read the descriptions, as defaults may be quite different!
139
- *
140
- * @param {object} obj - An object to test for a property in.
141
- * @param {string} optname - The property name we're checking for.
142
- * @param {*} defvalue - The default value.
143
- *
144
- * @param {(object|boolean)} [opts] Options
145
- *
146
- * If this is a `boolean` it is used as the `allowNull` option.
147
- *
148
- * @param {boolean} [opts.allowNull=true] Passed to `val()`;
149
- * default is `true`, which differs from `val()`.
150
- * @param {boolean} [opts.isLazy=false] Passed to `val()`;
151
- * default is `false`, the same as `val()`.
152
- * @param {object} [opts.lazyThis=obj] Passed to `val()`;
153
- * default is `obj`, which differs from `val()`.
154
- * @param {Array} [opts.lazyArgs] Passed to `val()`
155
- * @param {boolean} [opts.allowFun=false] Allow `obj` to be a `function` ?
156
- *
157
- * By default only `object` values are valid for `obj`; this can be set to
158
- * `true` to allow `function` values to be used.
159
- *
160
- * @param {boolean} [isLazy=false] Same as `opts.isLazy`
161
- * @param {object} [lazyThis=opts] Same as `opts.lazyThis`
162
- * @param {Array} [lazyArgs] Same as `opts.lazyArgs`
163
- * @param {boolean} [allowFun] Same as `opts.allowFun`
164
- *
165
- * @returns {*} Either the property value, or the default value.
166
- * @see module:@lumjs/core/opt.val
167
- * @alias module:@lumjs/core/opt.get
168
- */
169
- function get(obj, optname, defvalue,
170
- allowNull=true,
171
- isLazy=false,
172
- lazyThis=obj,
173
- lazyArgs=[],
174
- allowFun=false)
175
- {
176
- const opts = _opts({allowNull,isLazy,lazyThis,lazyArgs,allowFun}, true);
177
-
178
- needObj(obj, opts.allowFun);
179
- needType(S, optname);
180
-
181
- return val(obj[optname], defvalue,
182
- opts.allowNull,
183
- opts.isLazy,
184
- opts.lazyThis,
185
- opts.lazyArgs);
186
- }
187
-
188
- exports.get = get;
189
-
190
- /**
191
- * An alternative to `get()` that uses `getObjectPath()`
192
- * to look for a specific nested property value.
193
- *
194
- * While `get()` supports positional arguments like `val()`,
195
- * this function _only_ supports named options.
196
- *
197
- * @param {object} obj - Object we're looking for properties in
198
- * @param {(string|Array)} path - Path for `getObjectPath()`
199
- * @param {object} [opts] Options
200
- *
201
- * This supports all of the same options as `get()`, plus all of the
202
- * options supported by `getObjectPath()`. See the docs for both those
203
- * functions to see what all is supported. If the same option is supported
204
- * by *both* functions (e.g. `allowFun`) then the default value
205
- * will be the one from `getObjectPath()` rather than `get()`.
206
- *
207
- * @param {boolean} [opts.ro=false] Should `opts` be read-only?
208
- *
209
- * If `true`, a copy of the `opts` will be made before any changes
210
- * are performed, ensuring the original options aren't modified.
211
- *
212
- * @returns {*} The property if found, or `opts.default` if not.
213
- *
214
- * @see module:@lumjs/core/opt.get
215
- * @see module:@lumjs/core/obj.getObjectPath
216
- * @alias module:@lumjs/core/opt.getPath
217
- */
218
- function getPath(obj, path, opts={})
219
- {
220
- const defvalue = opts.default;
221
- if (opts.ro)
222
- {
223
- opts = Object.assign({}, opts);
224
- }
225
- delete opts.default;
226
-
227
- return val(getObjectPath(obj, path, opts), defvalue,
228
- (opts.allowNull ?? true),
229
- opts.isLazy,
230
- (opts.lazyThis ?? obj),
231
- opts.lazyArgs);
232
- }
233
-
234
- exports.getPath = getPath;
235
-
236
- /**
237
- * A class for handling options with multiple sources.
238
- * @alias module:@lumjs/core/opt.Opts
239
- */
240
- class Opts
241
- {
242
- /**
243
- * Build an Opts instance.
244
- *
245
- * @param {...object} sources - Initial sources of options.
246
- *
247
- * The order of sources matters, as the ones added later will override
248
- * the ones added earlier. Keep that in mind when adding sources.
249
- */
250
- constructor(...sources)
251
- {
252
- this.$sources = [];
253
- this.$curPos = -1;
254
- this.$curSrc = null;
255
-
256
- this.$fatalErrors = false;
257
- this.$strictProps = false;
258
-
259
- this.add(...sources);
260
- }
261
-
262
- static isPath(value)
263
- {
264
- return (Array.isArray(value)
265
- || (typeof value === S && value.includes('.')));
266
- }
267
-
268
- /**
269
- * Compile current sources into a data object.
270
- *
271
- * @returns {object} `this`
272
- * @private
273
- */
274
- _compile()
275
- {
276
- this.$data = Object.assign({}, ...this.$sources);
277
- return this;
278
- }
279
-
280
- /**
281
- * Handle an error
282
- *
283
- * @param {string} msg - A summary of the error.
284
- * @param {object} info - Debugging information for the logs.
285
- * @param {function} [errClass=TypeError] Constructor for an `Error` class.
286
- * Used if fatal errors are enabled.
287
- *
288
- * @returns {object} `this`
289
- * @throws {Error} An error of `errClass` class, if fatal mode is enabled.
290
- * @private
291
- */
292
- _err(msg, info={}, errClass=TypeError)
293
- {
294
- const args = this.$fatalErrors ? [info] : [msg, info];
295
-
296
- info.instance = this;
297
- console.error(...args);
298
-
299
- if (this.$fatalErrors)
300
- {
301
- throw new errClass(msg);
302
- }
303
- }
304
-
305
- /**
306
- * Normalize options.
307
- *
308
- * Auto-sets `isLazy` if it wasn't specified.
309
- * Applies any known aliases.
310
- *
311
- * @param {object} opts - Options to normalize
312
- * @returns {object} Usually `opts`, but might be a copy.
313
- * @private
314
- */
315
- _opts(opts)
316
- {
317
- if (opts._opts_compiled)
318
- { // Already done.
319
- return opts;
320
- }
321
-
322
- if (opts.ro)
323
- {
324
- opts = Object.assign({}, opts);
325
- }
326
-
327
- if (isNil(opts.isLazy)
328
- && (isComplex(opts.lazyThis)
329
- || isArray(opts.lazyArgs)))
330
- {
331
- opts.isLazy = true;
332
- }
333
-
334
- for (const akey in OPTS_ALIASES)
335
- {
336
- const okey = OPTS_ALIASES[akey];
337
- if (opts[okey] === undefined && opts[akey] !== undefined)
338
- { // An alias was found.
339
- opts[okey] = opts[akey];
340
- }
341
- }
342
-
343
- // Now remember that we've processed these options.
344
- opts._opts_compiled = true;
345
- return opts;
346
- }
347
-
348
- /**
349
- * Set the fatal error handling setting.
350
- *
351
- * Default is `false`, so errors will be logged, but not thrown.
352
- *
353
- * @param {boolean} val - Should errors be fatal?
354
- * @returns {object} `this`
355
- */
356
- fatal(val)
357
- {
358
- if (typeof val === B)
359
- {
360
- this.$fatalErrors = val;
361
- }
362
- else
363
- {
364
- this._err('invalid fatal value', {val});
365
- }
366
-
367
- return this;
368
- }
369
-
370
- /**
371
- * Set the strict property check setting.
372
- *
373
- * Default is `false`, we don't care about non-existent properties.
374
- *
375
- * @param {boolean} val - Should non-existant properties be an error?
376
- * @returns {object} `this`
377
- */
378
- strict(val)
379
- {
380
- if (typeof val === B)
381
- {
382
- this.$strictProps = val;
383
- }
384
- else
385
- {
386
- this._err('invalid strict value', {val});
387
- }
388
-
389
- return this;
390
- }
391
-
392
- /**
393
- * Set the position/offset to add new sources at.
394
- *
395
- * This will affect subsequent calls to the `add()` method.
396
- *
397
- * @param {number} pos - The position/offset value.
398
- *
399
- * - A value of `-1` uses `Array#push(src))`; end of array.
400
- * - A value of `0` uses `Array#unshift(src)`; start of array.
401
- * - Any value `> 0` uses `Array#splice(pos, 0, src)`; offset from start.
402
- * - Any value `< -1` uses `Array#splice(pos+1, 0, src)`; offset from end.
403
- *
404
- * The default value if none is specified is `-1`.
405
- *
406
- * @returns {object} `this`
407
- * @throws {TypeError} An invalid value was passed while `fatal` was true.
408
- */
409
- at(pos)
410
- {
411
- if (typeof pos === N)
412
- {
413
- this.$curPos = pos;
414
- }
415
- else
416
- {
417
- this._err("Invalid pos value", {pos});
418
- }
419
-
420
- return this;
421
- }
422
-
423
- /**
424
- * Set the object to look for nested properties in.
425
- *
426
- * This will affect subsequent calls to the `add()` method.
427
- *
428
- * @param {(object|number|boolean)} source - Source definition
429
- *
430
- * - If this is an `object` it will be used as the object directly.
431
- * - If this is a `number` it is the position of one of our data sources.
432
- * Negative numbers count from the end of the list of sources.
433
- * - If this is `true` then the compiled options data at the time of the
434
- * call to this method will be used.
435
- * - If this is `false` then the next time a `string` value is passed to
436
- * `add()` the options will be compiled on demand, and that object will
437
- * be used until the next call to `from()`.
438
- *
439
- * If this is not specified, then it defaults to `false`.
440
- *
441
- * @returns {object} `this`
442
- * @throws {TypeError} An invalid value was passed while `fatal` was true.
443
- */
444
- from(source)
445
- {
446
- if (source === true)
447
- { // Use existing data as the source.
448
- this.$curSrc = this.$data;
449
- }
450
- else if (source === false)
451
- { // Auto-generate the source the next time.
452
- this.$curSrc = null;
453
- }
454
- else if (typeof source === N)
455
- { // A number will be the position of an existing source.
456
- const offset
457
- = (source < 0)
458
- ? this.$sources.length + source
459
- : source;
460
-
461
- if (isObj(this.$sources[offset]))
462
- {
463
- this.$curSrc = this.$sources[offset];
464
- }
465
- else
466
- {
467
- this._err("Invalid source offset", {offset, source});
468
- }
469
- }
470
- else if (isObj(source))
471
- { // An object or function will be used as the source.
472
- this.$curSrc = source;
473
- }
474
- else
475
- {
476
- this._err("Invalid source", {source});
477
- }
478
-
479
- return this;
480
- }
481
-
482
- /**
483
- * Add new sources of options.
484
- *
485
- * @param {...(object|string)} sources - Sources and positions.
486
- *
487
- * If this is an `object` then it's a source of options to add.
488
- * This is the most common way of using this.
489
- *
490
- * If this is a `string` then it's assumed to be nested property
491
- * of the current `from()` source, and if that property exists and
492
- * is an object, it will be used as the source to add. If it does
493
- * not exist, then the behaviour will depend on the values of the
494
- * `strict()` and `fatal()` modifiers.
495
- *
496
- * @returns {object} `this`
497
- */
498
- add(...sources)
499
- {
500
- for (let source of sources)
501
- {
502
- if (source === undefined || source === null)
503
- { // Skip undefined or null values.
504
- continue;
505
- }
506
-
507
- if (typeof source === S)
508
- { // Try to find a nested property to include.
509
- if (this.$curSrc === null)
510
- { // Has not been initialized, let's do that now.
511
- this._compile();
512
- this.$curSrc = this.$data;
513
- }
514
-
515
- if (isObj(this.$curSrc[source]))
516
- { // Found a property, use it.
517
- source = this.$curSrc[source];
518
- }
519
- else
520
- { // No such property.
521
- if (this.$strictProps)
522
- {
523
- this._err('Property not found', {source});
524
- }
525
- continue;
526
- }
527
- }
528
-
529
- if (isObj(source))
530
- { // It's a source to add.
531
- insert(this.$sources, source, this.$curPos);
532
- }
533
- else
534
- { // That's not valid.
535
- this._err('invalid source value', {source, sources});
536
- }
537
- }
538
-
539
- return this._compile();
540
- }
541
-
542
- /**
543
- * Remove existing sources of options.
544
- *
545
- * @param {...object} sources - Sources to remove.
546
- *
547
- * @returns {object} `this`
548
- */
549
- remove(...sources)
550
- {
551
- for (const source of sources)
552
- {
553
- const index = this.$sources.indexOf(source);
554
- if (index !== -1)
555
- {
556
- this.$sources.splice(index, 1);
557
- }
558
- }
559
-
560
- return this._compile();
561
- }
562
-
563
- /**
564
- * Remove all current sources. Resets compiled options data.
565
- *
566
- * @returns {object} `this`
567
- */
568
- clear()
569
- {
570
- this.$sources = [];
571
- return this._compile();
572
- }
573
-
574
- /**
575
- * Get an option value from our compiled data sources.
576
- *
577
- * This uses either `get()` or `getPath()` depending on
578
- * the specified arguments.
579
- *
580
- * @param {(string|Array)} opt - The name or path of the option to get.
581
- *
582
- * @param {object} [opts] Options
583
- *
584
- * I will only list the options that are specific to this method,
585
- * as the rest are already documented in `getPath()` and related functions.
586
- *
587
- * Note that some options like `opts.ro` and `opts.default` which are
588
- * supported by `getPath()` but not `get()` are usable here regardless
589
- * of which of those functions will end up being called. The method
590
- * will do the right thing to make those options work in every context.
591
- *
592
- * @param {boolean} [opts.path] Use `getPath()` instead of `get()` ?
593
- *
594
- * If not specified, this will be auto-determined based on the `opt`;
595
- * if `opt` is an `Array` or contains the `'.'` character the default
596
- * will be `true`, otherwise it will be `false`.
597
- *
598
- * @returns {*} The output of the `get()` function.
599
- * @see module:@lumjs/core/opt.getPath
600
- */
601
- get(opt, opts={})
602
- {
603
- opts = this._opts(opts);
604
-
605
- const isPath = this.constructor.isPath;
606
- const usePath = opts.path ?? isPath(opt);
607
-
608
- if (usePath)
609
- {
610
- return getPath(this.$data, opt, opts);
611
- }
612
- else
613
- {
614
- return get(this.$data, opt, opts.default, opts);
615
- }
616
- }
617
-
618
- /**
619
- * A wrapper around the `get()` method that can check for
620
- * multiple possible properties or namespaces, and will
621
- * return the first one that has a defined value.
622
- *
623
- * @param {object} opts - Options
624
- *
625
- * The same options as the `get()` instance method, which includes all
626
- * of the options of the `getPath()`, `get()`, and `getObjectPath()`
627
- * utility functions. So there's a lot of options supported here.
628
- *
629
- * Unlike every other method and function that uses options,
630
- * this is a mandatory argument. You cannot skip it. If you want
631
- * to use all default options, just pass `{}` and presto, defaults.
632
- *
633
- * @param {...(string|Array)} paths - All the properties/paths to try.
634
- *
635
- * At least one path must be specified (although if you were only
636
- * going to specify one, you may as well use the `get()` method
637
- * directly rather than this...)
638
- *
639
- * @returns {*} Could be anything!
640
- */
641
- find(opts, ...paths)
642
- {
643
- opts = this._opts(opts);
644
-
645
- const defvalue = opts.default;
646
- delete opts.default;
647
- delete opts.ro;
648
-
649
- for (const path of paths)
650
- {
651
- const value = this.get(path, opts);
652
- if (value !== undefined)
653
- { // Found a value.
654
- return value;
655
- }
656
- }
657
-
658
- // No matches found, use the default.
659
- return val(undefined, defvalue, opts);
660
- }
661
-
662
- } // Opts
663
-
664
- exports.Opts = Opts;