@lumjs/core 1.25.2 → 1.26.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/TODO.md CHANGED
@@ -17,6 +17,8 @@ for the future would be a good idea. A few of the things I want to do:
17
17
  - Cut anything that seems superfluous or rarely used
18
18
  - Add ability to copy `Symbol` properties
19
19
  - Replace `obj.syncNested` with `obj.sync` using the new `obj.copy` API
20
+ - Move `opt.Opts` into its own separate package
21
+ - Give `observable` some TLC
20
22
 
21
23
  I will likely update this list a bit before I get around to starting the
22
24
  new branch that will eventually become the `2.0.0` release.
package/lib/obj/ns.js CHANGED
@@ -83,6 +83,11 @@ exports.nsArray = nsArray;
83
83
  * @param {(object|boolean)} [opts] Options changing the behaviours.
84
84
  * If this is a `boolean` it's assumed to be the `opts.log` option.
85
85
  * @param {boolean} [opts.log=false] Log errors for missing namespaces?
86
+ * @param {boolean} [opts.allowFun=true] Allow `obj` to be a `function` ?
87
+ *
88
+ * By default both `object` and `function` are valid `obj` argument values;
89
+ * if this is set to `false`, only `object` values will be allowed.
90
+ *
86
91
  * @param {*} [opts.default] A default value if the namespace is not found.
87
92
  *
88
93
  * @return {*} The property if found, or `opts.default` if not.
@@ -90,13 +95,13 @@ exports.nsArray = nsArray;
90
95
  */
91
96
  function getObjectPath(obj, proppath, opts={})
92
97
  {
93
- needObj(obj, true);
94
-
95
98
  if (typeof opts === B)
96
99
  opts = {log: opts};
97
100
  else if (!isObj(opts))
98
101
  opts = {};
99
102
 
103
+ needObj(obj, (opts.allowFun ?? true));
104
+
100
105
  proppath = nsArray(proppath);
101
106
 
102
107
  for (let p = 0; p < proppath.length; p++)
package/lib/opt.js CHANGED
@@ -3,25 +3,109 @@
3
3
  * @module @lumjs/core/opt
4
4
  */
5
5
 
6
- const {U,F,S,N,B,isObj,isComplex,needObj,needType} = require('./types');
6
+ const
7
+ {
8
+ U,F,S,N,B,
9
+ isObj,isComplex,isArray,isNil,needObj,needType
10
+ } = require('./types');
7
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
+ }
8
79
 
9
80
  /**
10
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.
11
87
  *
12
88
  * @param {*} optvalue - The value we are testing.
13
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.
14
94
  *
15
- * @param {boolean} [allowNull=false] If true, allow null to count as *set*.
16
- * @param {boolean} [isLazy=false] If true, and `defvalue` is a function,
17
- * use the value from the function as
18
- * the default.
19
- * @param {object} [lazyThis=null] If `isLazy` is true, this object will
20
- * be used as `this` for the function.
21
- * @param {Array} [lazyArgs] If `isLazy` is true, this may be used
22
- * as a list of arguments to pass.
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`
23
107
  *
24
- * @return {*} Either the specified `opt` value or the default value.
108
+ * @return {*} Either `optvalue` or `defvalue` depending on the test.
25
109
  * @alias module:@lumjs/core/opt.val
26
110
  */
27
111
  function val(optvalue, defvalue,
@@ -30,11 +114,13 @@ function val(optvalue, defvalue,
30
114
  lazyThis=null,
31
115
  lazyArgs=[])
32
116
  {
33
- if (typeof optvalue === U || (!allowNull && optvalue === null))
117
+ const opts = _opts({allowNull,isLazy,lazyThis,lazyArgs}, false);
118
+
119
+ if (typeof optvalue === U || (!opts.allowNull && optvalue === null))
34
120
  { // The defined value was not "set" as per our rules.
35
- if (isLazy && typeof defvalue === F)
121
+ if (opts.isLazy && typeof defvalue === F)
36
122
  { // Get the default value from a passed in function.
37
- return defvalue.apply(lazyThis, lazyArgs);
123
+ return defvalue.apply(opts.lazyThis, opts.lazyArgs);
38
124
  }
39
125
  return defvalue;
40
126
  }
@@ -48,34 +134,105 @@ exports.val = val;
48
134
  * See if a property in an object is set.
49
135
  *
50
136
  * If it is, return the property, otherwise return a default value.
51
- * This uses the `val()` method, and as such supports the same options.
52
- * However read the parameters carefully, as the defaults may be different!
137
+ * This uses the `val()` method, and as such supports the same arguments.
138
+ * However read the descriptions, as defaults may be quite different!
53
139
  *
54
- * @param {object} obj - An object to test for a property in.
55
- * @param {string} optname - The property name we're checking for.
56
- * @param {*} defvalue - The default value.
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.
57
143
  *
58
- * @param {bool} [allowNull=true] Same as `val()`, but default is `true`.
59
- * @param {bool} [isLazy=false] Same as `val()`.
60
- * @param {object} [lazyThis=opts] Same as `val()`, but default is `obj`.
61
- * @param {Array} [lazyArgs] Same as `val()`.
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`
62
164
  *
63
- * @return {*} Either the property value, or the default value.
165
+ * @returns {*} Either the property value, or the default value.
166
+ * @see module:@lumjs/core/opt.val
64
167
  * @alias module:@lumjs/core/opt.get
65
168
  */
66
169
  function get(obj, optname, defvalue,
67
170
  allowNull=true,
68
171
  isLazy=false,
69
172
  lazyThis=obj,
70
- lazyArgs)
173
+ lazyArgs=[],
174
+ allowFun=false)
71
175
  {
72
- needObj(obj);
176
+ const opts = _opts({allowNull,isLazy,lazyThis,lazyArgs,allowFun}, true);
177
+
178
+ needObj(obj, opts.allowFun);
73
179
  needType(S, optname);
74
- return val(obj[optname], defvalue, allowNull, isLazy, lazyThis, lazyArgs);
180
+
181
+ return val(obj[optname], defvalue,
182
+ opts.allowNull,
183
+ opts.isLazy,
184
+ opts.lazyThis,
185
+ opts.lazyArgs);
75
186
  }
76
187
 
77
188
  exports.get = get;
78
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
+
79
236
  /**
80
237
  * A class for handling options with multiple sources.
81
238
  * @alias module:@lumjs/core/opt.Opts
@@ -100,7 +257,12 @@ class Opts
100
257
  this.$strictProps = false;
101
258
 
102
259
  this.add(...sources);
103
- this._compile();
260
+ }
261
+
262
+ static isPath(value)
263
+ {
264
+ return (Array.isArray(value)
265
+ || (typeof value === S && value.includes('.')));
104
266
  }
105
267
 
106
268
  /**
@@ -125,6 +287,7 @@ class Opts
125
287
  *
126
288
  * @returns {object} `this`
127
289
  * @throws {Error} An error of `errClass` class, if fatal mode is enabled.
290
+ * @private
128
291
  */
129
292
  _err(msg, info={}, errClass=TypeError)
130
293
  {
@@ -139,6 +302,49 @@ class Opts
139
302
  }
140
303
  }
141
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
+
142
348
  /**
143
349
  * Set the fatal error handling setting.
144
350
  *
@@ -291,8 +497,6 @@ class Opts
291
497
  */
292
498
  add(...sources)
293
499
  {
294
- let pos=-1;
295
-
296
500
  for (let source of sources)
297
501
  {
298
502
  if (source === undefined || source === null)
@@ -324,7 +528,7 @@ class Opts
324
528
 
325
529
  if (isObj(source))
326
530
  { // It's a source to add.
327
- insert(this.$sources, source, pos);
531
+ insert(this.$sources, source, this.$curPos);
328
532
  }
329
533
  else
330
534
  { // That's not valid.
@@ -370,35 +574,91 @@ class Opts
370
574
  /**
371
575
  * Get an option value from our compiled data sources.
372
576
  *
373
- * This uses the `get()` function, but instead of using positional
374
- * arguments, it supports an object of named options instead.
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.
375
586
  *
376
- * @param {string} opt - The name of the option to get.
377
- * @param {object} [args] Optional arguments for `get()`.
378
- * @param {*} [args.default] `defvalue` argument.
379
- * @param {boolean} [args.null=true] `allowNull` argument.
380
- * @param {boolean} [args.lazy=false] `isLazy` argument.
381
- * @param {(object|function)} [args.lazyThis] `lazyThis` argument.
382
- * If this is defined, `args.lazy` will be set to `true`.
383
- * @param {Array} [args.lazyArgs] `lazyArgs` argument.
384
- * If this is defined, `args.lazy` will be set to `true`.
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`.
385
597
  *
386
598
  * @returns {*} The output of the `get()` function.
599
+ * @see module:@lumjs/core/opt.getPath
387
600
  */
388
- get(opt, args={})
601
+ get(opt, opts={})
389
602
  {
390
- if (isComplex(args.lazyThis) || Array.isArray(args.lazyArgs))
603
+ opts = this._opts(opts);
604
+
605
+ const isPath = this.constructor.isPath;
606
+ const usePath = opts.path ?? isPath(opt);
607
+
608
+ if (usePath)
391
609
  {
392
- args.lazy = true;
610
+ return getPath(this.$data, opt, opts);
393
611
  }
612
+ else
613
+ {
614
+ return get(this.$data, opt, opts.default, opts);
615
+ }
616
+ }
394
617
 
395
- return get(this.$data, opt,
396
- args.default,
397
- args.null,
398
- args.lazy,
399
- args.lazyThis,
400
- args.lazyArgs);
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);
401
660
  }
402
- }
661
+
662
+ } // Opts
403
663
 
404
664
  exports.Opts = Opts;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumjs/core",
3
- "version": "1.25.2",
3
+ "version": "1.26.0",
4
4
  "main": "lib/index.js",
5
5
  "exports":
6
6
  {