@lumjs/core 1.33.0 → 1.35.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/console.js +1 -1
- package/lib/events/index.js +15 -1
- package/lib/events/listener.js +6 -0
- package/lib/events/observable.js +243 -0
- package/lib/events/registry.js +12 -3
- package/lib/obj/cp.js +2 -2
- package/lib/observable.js +62 -319
- package/lib/old/observable.js +291 -0
- package/lib/traits.js +83 -66
- package/lib/types/basics.js +34 -1
- package/lib/types/index.js +1 -1
- package/lib/types/stringify.js +249 -90
- package/package.json +2 -1
package/lib/traits.js
CHANGED
|
@@ -10,13 +10,18 @@ const getProp = require('./obj/getproperty');
|
|
|
10
10
|
const
|
|
11
11
|
{
|
|
12
12
|
def,F,B,S,
|
|
13
|
-
isObj,isArray,isConstructor,isProperty,
|
|
13
|
+
isObj,isArray,isConstructor,isProperty,isClassObject,
|
|
14
14
|
needObj,needType,
|
|
15
15
|
} = require('./types');
|
|
16
16
|
|
|
17
17
|
// Symbol for private storage of composed traits.
|
|
18
18
|
const COMPOSED_TRAITS = Symbol.for('@lumjs/core/traits~ComposedTraits');
|
|
19
19
|
|
|
20
|
+
const FLAG_ET = 1; // Flag for ensure target
|
|
21
|
+
const FLAG_ES = 2; // Flag for ensure source
|
|
22
|
+
const FLAGS_S = 0; // Default flags for static mode
|
|
23
|
+
const FLAGS_O = 3; // Default flags for object mode
|
|
24
|
+
|
|
20
25
|
/**
|
|
21
26
|
* For when we need a prototype object exclusively.
|
|
22
27
|
*
|
|
@@ -30,7 +35,7 @@ const COMPOSED_TRAITS = Symbol.for('@lumjs/core/traits~ComposedTraits');
|
|
|
30
35
|
* If `from` is a `function` then `from.prototype` will be returned.
|
|
31
36
|
*
|
|
32
37
|
* @throws {TypeError} If `from` was not a valid value;
|
|
33
|
-
* including if it is a
|
|
38
|
+
* including if it is a function but does not have a valid
|
|
34
39
|
* prototype object to return.
|
|
35
40
|
*
|
|
36
41
|
* @alias module:@lumjs/core/traits.ensureProto
|
|
@@ -47,7 +52,42 @@ function ensureProto(from)
|
|
|
47
52
|
}
|
|
48
53
|
else
|
|
49
54
|
{
|
|
50
|
-
throw new TypeError("Invalid class constructor or object
|
|
55
|
+
throw new TypeError("Invalid class constructor or instance object");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* For when we need a constructor function exclusively.
|
|
61
|
+
*
|
|
62
|
+
* @param {(function|object)} from - Target
|
|
63
|
+
*
|
|
64
|
+
* May either be a class constructor `function`, or an `object` instance.
|
|
65
|
+
*
|
|
66
|
+
* @returns {function}
|
|
67
|
+
*
|
|
68
|
+
* If `from` is a constructor function it will be returned as-is.
|
|
69
|
+
* If `from` is an object instance (with a constructor other than `Object`),
|
|
70
|
+
* then the `from.constructor` value will be returned.
|
|
71
|
+
*
|
|
72
|
+
* @throws {TypeError} If `from` was not a valid value;
|
|
73
|
+
* including if it was a function without a prototype,
|
|
74
|
+
* or an object with a constructor of `Object`.
|
|
75
|
+
*
|
|
76
|
+
* @alias module:@lumjs/core/traits.ensureConstructor
|
|
77
|
+
*/
|
|
78
|
+
function ensureConstructor(from)
|
|
79
|
+
{
|
|
80
|
+
if (isConstructor(from))
|
|
81
|
+
{ // Good to go
|
|
82
|
+
return from;
|
|
83
|
+
}
|
|
84
|
+
else if (isClassObject(from))
|
|
85
|
+
{ // An instance of a class (other than `Object` itself)
|
|
86
|
+
return from.constructor;
|
|
87
|
+
}
|
|
88
|
+
else
|
|
89
|
+
{
|
|
90
|
+
throw new TypeError("Invalid class constructor or instance object");
|
|
51
91
|
}
|
|
52
92
|
}
|
|
53
93
|
|
|
@@ -125,13 +165,16 @@ function getComposed(target, source, tryClass=true)
|
|
|
125
165
|
composed = target[COMPOSED_TRAITS];
|
|
126
166
|
}
|
|
127
167
|
|
|
128
|
-
if (composed
|
|
129
|
-
{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
168
|
+
if (composed)
|
|
169
|
+
{
|
|
170
|
+
if (!source)
|
|
171
|
+
{
|
|
172
|
+
return composed;
|
|
173
|
+
}
|
|
174
|
+
if (composed.has(source))
|
|
175
|
+
{ // Let's get definitions for a specific trait.
|
|
176
|
+
return composed.get(source);
|
|
177
|
+
}
|
|
135
178
|
}
|
|
136
179
|
|
|
137
180
|
if (tryClass && isObj(target))
|
|
@@ -224,9 +267,27 @@ function mapComposed(target, source)
|
|
|
224
267
|
* or use the {@link module:@lumjs/core/traits.composeFully} function.
|
|
225
268
|
*
|
|
226
269
|
* Only useful if `source` is a class constructor (always recommended).
|
|
270
|
+
* The exact behaviour may be customized further with the `.flags` option.
|
|
227
271
|
*
|
|
228
272
|
* Default is `false`.
|
|
229
273
|
*
|
|
274
|
+
* @param {number} [opts.flags] Binary flags for advanced behaviours
|
|
275
|
+
*
|
|
276
|
+
* The default `.flags` value, and the _ensure function_ used depends
|
|
277
|
+
* on the value of the `.static` option:
|
|
278
|
+
*
|
|
279
|
+
* | .static | Default | Ensure function |
|
|
280
|
+
* | ------- | ------- | --------------------------------------------------- |
|
|
281
|
+
* | `false` | `3` | {@link module:@lumjs/core/traits.ensureProto} |
|
|
282
|
+
* | `true` | `0` | {@link module:@lumjs/core/traits.ensureConstructor} |
|
|
283
|
+
*
|
|
284
|
+
* The actual flags are fairly straightforward:
|
|
285
|
+
*
|
|
286
|
+
* | Flag | Description |
|
|
287
|
+
* | ---- | ---------------------------------------------------------------- |
|
|
288
|
+
* | `1` | Pass `target` through _ensure function_ |
|
|
289
|
+
* | `2` | Pass `source` through _ensure function_ |
|
|
290
|
+
*
|
|
230
291
|
* @param {boolean} [opts.symbols=false] Auto-compose `Symbol` properties?
|
|
231
292
|
*
|
|
232
293
|
* Calls `Object.getOwnPropertySymbols()` when generating a list of
|
|
@@ -260,8 +321,7 @@ function compose(specTarget, specSource, opts={})
|
|
|
260
321
|
= getComposed(specTarget, specSource)
|
|
261
322
|
?? mapComposed(specTarget, specSource);
|
|
262
323
|
|
|
263
|
-
const isStatic = opts.static ?? false;
|
|
264
|
-
|
|
324
|
+
const isStatic = opts.static ?? false;
|
|
265
325
|
const mapId = isStatic ? 'static' : 'proto';
|
|
266
326
|
|
|
267
327
|
if (composedMaps[mapId])
|
|
@@ -270,8 +330,11 @@ function compose(specTarget, specSource, opts={})
|
|
|
270
330
|
return composedMaps;
|
|
271
331
|
}
|
|
272
332
|
|
|
273
|
-
const
|
|
274
|
-
const
|
|
333
|
+
const flags = parseInt(opts.flags) ?? (isStatic ? FLAGS_S : FLAGS_O);
|
|
334
|
+
const ensure = isStatic ? ensureConstructor : ensureProto;
|
|
335
|
+
|
|
336
|
+
const target = (flags & FLAG_ET) ? specTarget : ensure(specTarget);
|
|
337
|
+
const source = (flags & FLAG_ES) ? specSource : ensure(specSource);
|
|
275
338
|
|
|
276
339
|
let props = opts.props;
|
|
277
340
|
|
|
@@ -382,7 +445,6 @@ function compose(specTarget, specSource, opts={})
|
|
|
382
445
|
composed.set(tprop, cdef);
|
|
383
446
|
}
|
|
384
447
|
|
|
385
|
-
composedMaps[mapId] = composed;
|
|
386
448
|
return composedMaps;
|
|
387
449
|
}
|
|
388
450
|
|
|
@@ -642,9 +704,10 @@ class CoreTrait
|
|
|
642
704
|
* @param {boolean} info.ok - Will always be `true` initially.
|
|
643
705
|
*
|
|
644
706
|
* If an overridden `removeTrait()` method sets this to `false`,
|
|
645
|
-
* then the
|
|
646
|
-
* decomposing the trait.
|
|
707
|
+
* then the decomposeFrom() operation will skip the step of
|
|
708
|
+
* actually decomposing the trait.
|
|
647
709
|
*
|
|
710
|
+
* @returns {*} Return value is not used.
|
|
648
711
|
*/
|
|
649
712
|
static removeTrait(info)
|
|
650
713
|
{
|
|
@@ -714,7 +777,7 @@ class CoreTrait
|
|
|
714
777
|
* @returns {function} The `registerTrait()` function created above.
|
|
715
778
|
* @alias module:@lumjs/core/traits.makeRegistry
|
|
716
779
|
*/
|
|
717
|
-
function makeTraitRegistry(registry)
|
|
780
|
+
function makeTraitRegistry(registry={})
|
|
718
781
|
{
|
|
719
782
|
needObj(registry, false, 'invalid trait registry object');
|
|
720
783
|
|
|
@@ -749,56 +812,10 @@ function makeTraitRegistry(registry)
|
|
|
749
812
|
module.exports =
|
|
750
813
|
{
|
|
751
814
|
compose, composeFully, getComposed, decompose,
|
|
752
|
-
Trait: CoreTrait, IGNORE_STATIC,
|
|
815
|
+
Trait: CoreTrait, IGNORE_STATIC,
|
|
816
|
+
ensureProto, ensureConstructor,
|
|
753
817
|
makeRegistry: makeTraitRegistry,
|
|
754
818
|
|
|
755
819
|
// Undocumented:
|
|
756
820
|
hasOwn,
|
|
757
821
|
}
|
|
758
|
-
|
|
759
|
-
/**
|
|
760
|
-
* Composed property maps.
|
|
761
|
-
*
|
|
762
|
-
* @typedef {object} module:@lumjs/core/traits~Composed
|
|
763
|
-
*
|
|
764
|
-
* @prop {?Map} proto - Map of composed *prototype* properties.
|
|
765
|
-
*
|
|
766
|
-
* Keys are the `string` or `symbol` for each composed property.
|
|
767
|
-
* Values are {@link module:@lumjs/core/traits~ComposedProperty} objects.
|
|
768
|
-
*
|
|
769
|
-
* If no applicable trait properties are currently composed,
|
|
770
|
-
* this will be `null`.
|
|
771
|
-
*
|
|
772
|
-
* @prop {?Map} static - Map of composed *static* properties.
|
|
773
|
-
*
|
|
774
|
-
* Exactly the same description as `proto`, but for static properties.
|
|
775
|
-
*
|
|
776
|
-
* @prop {boolean} forClass - Are these property maps from a class?
|
|
777
|
-
*
|
|
778
|
-
* Will be `true` if the original target was a `function`, or `false`
|
|
779
|
-
* otherwise. Only really useful when trait functions need to be called
|
|
780
|
-
* differently depending on if the trait was applied to an individual
|
|
781
|
-
* instance or a class constructor.
|
|
782
|
-
*
|
|
783
|
-
*/
|
|
784
|
-
|
|
785
|
-
/**
|
|
786
|
-
* Composed property definitions.
|
|
787
|
-
*
|
|
788
|
-
* @typedef {object} module:@lumjs/core/traits~ComposedProperty
|
|
789
|
-
*
|
|
790
|
-
* @prop {(string|Symbol)} id - The property key on the `target`
|
|
791
|
-
* @prop {(string|Symbol)} [srcKey] The property key from the `source`;
|
|
792
|
-
* only included if different from `propKey`.
|
|
793
|
-
*
|
|
794
|
-
* @prop {?object} newDescriptor - The property descriptor to be added;
|
|
795
|
-
* will be `null` if the property did not exist.
|
|
796
|
-
*
|
|
797
|
-
* @prop {object} [oldDescriptor] The replaced property descriptor;
|
|
798
|
-
* only included if there was an existing property in the `target`
|
|
799
|
-
* and the `opts.overwrite` option was `true`.
|
|
800
|
-
*
|
|
801
|
-
* @prop {boolean} found - Was the property found in the `source` ?
|
|
802
|
-
* @prop {boolean} added - Was the property added to the `target` ?
|
|
803
|
-
*
|
|
804
|
-
*/
|
package/lib/types/basics.js
CHANGED
|
@@ -144,6 +144,9 @@ function isIterable(v)
|
|
|
144
144
|
/**
|
|
145
145
|
* Does a value appear to be a class constructor?
|
|
146
146
|
*
|
|
147
|
+
* For the purposes of this test, a class constructor is
|
|
148
|
+
* any Function that has a `prototype` Object property.
|
|
149
|
+
*
|
|
147
150
|
* @param {*} v - Value to test
|
|
148
151
|
* @returns {boolean}
|
|
149
152
|
* @alias module:@lumjs/core/types/basics.isConstructor
|
|
@@ -153,6 +156,35 @@ function isConstructor(v)
|
|
|
153
156
|
return (typeof v === F && isObj(v.prototype));
|
|
154
157
|
}
|
|
155
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Is a value a _plain_ object?
|
|
161
|
+
*
|
|
162
|
+
* A plain object is one which has a `constructor` property
|
|
163
|
+
* that **IS** the `globalThis.Object` function.
|
|
164
|
+
*
|
|
165
|
+
* @param {*} v - Value to test
|
|
166
|
+
* @returns {boolean}
|
|
167
|
+
*/
|
|
168
|
+
function isPlainObject(v)
|
|
169
|
+
{
|
|
170
|
+
return (isObj(v) && v.constructor === Object);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Is a value a class instance object?
|
|
175
|
+
*
|
|
176
|
+
* This is a counterpart to isPlainObject() that only returns
|
|
177
|
+
* true if the `constructor` property exists, but is **NOT**
|
|
178
|
+
* the `globalThis.Object` function.
|
|
179
|
+
*
|
|
180
|
+
* @param {*} v - Value to test
|
|
181
|
+
* @returns {boolean}
|
|
182
|
+
*/
|
|
183
|
+
function isClassObject(v)
|
|
184
|
+
{
|
|
185
|
+
return (isObj(v) && typeof v.constructor === F && v.constructor !== Object);
|
|
186
|
+
}
|
|
187
|
+
|
|
156
188
|
/**
|
|
157
189
|
* See if an object can be used as a valid *descriptor*.
|
|
158
190
|
*
|
|
@@ -246,7 +278,8 @@ module.exports =
|
|
|
246
278
|
{
|
|
247
279
|
isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
|
|
248
280
|
isIterable, nonEmptyArray, isArguments, isProperty, isConstructor,
|
|
249
|
-
doesDescriptor, doesDescriptorTemplate,
|
|
281
|
+
isPlainObject, isClassObject, doesDescriptor, doesDescriptorTemplate,
|
|
282
|
+
JS,
|
|
250
283
|
}
|
|
251
284
|
|
|
252
285
|
JS.addTo(module.exports);
|
package/lib/types/index.js
CHANGED
|
@@ -16,11 +16,11 @@ Object.assign(exports,
|
|
|
16
16
|
require('./basics'),
|
|
17
17
|
require('./root'),
|
|
18
18
|
require('./isa'),
|
|
19
|
+
require('./stringify'),
|
|
19
20
|
require('./needs'),
|
|
20
21
|
{ // A few standalone exports.
|
|
21
22
|
def, lazy,
|
|
22
23
|
TYPES: require('./typelist'),
|
|
23
|
-
stringify: require('./stringify'),
|
|
24
24
|
ownCount: require('./owncount'),
|
|
25
25
|
},
|
|
26
26
|
);
|
package/lib/types/stringify.js
CHANGED
|
@@ -1,142 +1,301 @@
|
|
|
1
1
|
// Get the extended type list.
|
|
2
2
|
const TYPES = require('./typelist');
|
|
3
|
-
const {isObj, isArray, isTypedArray} = require('./basics');
|
|
3
|
+
const {F, S, N, B, isObj, isArray, isTypedArray} = require('./basics');
|
|
4
4
|
const def = require('./def');
|
|
5
5
|
|
|
6
|
-
const TOSTRING_TYPES
|
|
6
|
+
const TOSTRING_TYPES = [TYPES.SY];
|
|
7
7
|
const TOSTRING_INSTANCES = [RegExp];
|
|
8
8
|
const CUSTOM = [];
|
|
9
|
+
const DEF =
|
|
10
|
+
{
|
|
11
|
+
MAXD: 1,
|
|
12
|
+
NEWD: 0,
|
|
13
|
+
}
|
|
9
14
|
|
|
10
15
|
/**
|
|
11
16
|
* Stringify a Javascript value.
|
|
12
17
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
18
|
+
* Creates a new `Stringify` instance, then passes the
|
|
19
|
+
* first argument to it's `stringify()` method.
|
|
20
|
+
*
|
|
21
|
+
* This is the primary way to use the Stringify class
|
|
22
|
+
* rather than manually creating an instance of it.
|
|
23
|
+
*
|
|
24
|
+
* @param {*} what - Passed to `instance.stringify()`
|
|
25
|
+
* @param {(object|number)} [opts] Options for Stringify constructor;
|
|
26
|
+
*
|
|
27
|
+
* If this is a `number` it's the `opts.maxDepth` option value.
|
|
28
|
+
*
|
|
29
|
+
* @param {(number|boolean)} [addNew] The `opts.newDepth` option value;
|
|
30
|
+
*
|
|
31
|
+
* For compatibility with old API. Boolean values are shortcuts:
|
|
32
|
+
* - `false` = `0`
|
|
33
|
+
* - `true` = `1`
|
|
34
|
+
*
|
|
35
|
+
* @returns {string} The stringified value
|
|
36
|
+
*
|
|
37
|
+
* @alias module:@lumjs/core/types.stringify
|
|
38
|
+
* @see module:@lumjs/core/types.Stringify
|
|
39
|
+
* @see module:@lumjs/core/types.Stringify#stringify
|
|
40
|
+
*/
|
|
41
|
+
function stringify (what, opts={}, addNew=null)
|
|
42
|
+
{
|
|
43
|
+
if (typeof opts === N)
|
|
44
|
+
{
|
|
45
|
+
opts = {maxDepth: opts}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof addNew === N)
|
|
49
|
+
{
|
|
50
|
+
opts.newDepth = addNew;
|
|
51
|
+
}
|
|
52
|
+
else if (typeof addNew === B)
|
|
53
|
+
{
|
|
54
|
+
opts.newDepth = addNew ? 1 : 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const si = new Stringify(opts);
|
|
58
|
+
return si.stringify(what);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A class to stringify Javascript values for testing and debugging.
|
|
63
|
+
*
|
|
64
|
+
* This is NOT meant for serializing data, the output is in a format
|
|
65
|
+
* that's meant to be read by a developer, not parsed by a machine.
|
|
66
|
+
*
|
|
67
|
+
* Currently directly supports:
|
|
68
|
+
*
|
|
15
69
|
* - `function`
|
|
16
70
|
* - `symbol`
|
|
17
71
|
* - `TypedArray`
|
|
18
72
|
* - `Map`
|
|
19
73
|
* - `Set`
|
|
20
74
|
* - `Error`
|
|
21
|
-
*
|
|
22
|
-
*
|
|
75
|
+
* - `RegExp`
|
|
76
|
+
* - `Object`
|
|
23
77
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
78
|
+
* Any other JS value types (`number`,`boolean`, `string`, etc.) will
|
|
79
|
+
* be serialized using JSON.
|
|
26
80
|
*
|
|
27
|
-
* @
|
|
28
|
-
* @param {integer} [recurse=1] Recurse objects to this depth.
|
|
29
|
-
* @param {boolean} [addNew=false] Use 'new Class()' instead of 'Class()'.
|
|
30
|
-
* @returns {string} The stringified value.
|
|
31
|
-
* @alias module:@lumjs/core/types.stringify
|
|
81
|
+
* @alias module:@lumjs/core/types.Stringify
|
|
32
82
|
*/
|
|
33
|
-
|
|
83
|
+
class Stringify
|
|
34
84
|
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Create a new Stringify instance
|
|
87
|
+
* @param {object} [opts] Options; saved to `this.options`
|
|
88
|
+
* @param {number} [opts.maxDepth=1] Max depth to recurse?
|
|
89
|
+
* @param {number} [opts.newDepth=0] Depth to prepend 'new' to strings?
|
|
90
|
+
* @param {boolean} [opts.fnString=false] Fully stringify functions?
|
|
91
|
+
* @param {(Array|function)} [opts.setCustom] Set custom stringifiers;
|
|
92
|
+
* used *INSTEAD* of the globally registered ones.
|
|
93
|
+
* @param {(Array|function)} [opts.addCustom] Add custom stringifiers;
|
|
94
|
+
* used *in addition* to the globally registered ones.
|
|
95
|
+
*/
|
|
96
|
+
constructor(opts={})
|
|
97
|
+
{
|
|
98
|
+
this.options = opts;
|
|
99
|
+
this.maxDepth = opts.maxDepth ?? DEF.MAXD;
|
|
100
|
+
this.newDepth = opts.newDepth ?? DEF.NEWD;
|
|
101
|
+
this.seenObjects = new Map(); // object => description
|
|
102
|
+
this.seenDescrip = new Map(); // description => count
|
|
103
|
+
this.strClass = TOSTRING_INSTANCES.slice();
|
|
104
|
+
this.strTypes = TOSTRING_TYPES.slice();
|
|
45
105
|
|
|
46
|
-
|
|
47
|
-
|
|
106
|
+
if (opts.fnString)
|
|
107
|
+
{
|
|
108
|
+
this.strTypes.push(F);
|
|
109
|
+
}
|
|
48
110
|
|
|
49
|
-
|
|
50
|
-
|
|
111
|
+
if (opts.errString)
|
|
112
|
+
{
|
|
113
|
+
this.strClass.push(Error);
|
|
114
|
+
}
|
|
51
115
|
|
|
52
|
-
|
|
53
|
-
|
|
116
|
+
if (Array.isArray(opts.setCustom))
|
|
117
|
+
{
|
|
118
|
+
this.customFn = opts.setCustom.slice();
|
|
119
|
+
}
|
|
120
|
+
else if (typeof opts.setCustom === F)
|
|
54
121
|
{
|
|
55
|
-
|
|
122
|
+
this.customFn = [];
|
|
123
|
+
opts.setCustom.call(this, this.customFn);
|
|
124
|
+
}
|
|
125
|
+
else
|
|
126
|
+
{ // Start out with the global custom tests.
|
|
127
|
+
this.customFn = CUSTOM.slice();
|
|
128
|
+
if (Array.isArray(opts.addCustom))
|
|
129
|
+
{
|
|
130
|
+
this.customFn.push(...opts.addCustom);
|
|
131
|
+
}
|
|
132
|
+
else if (typeof opts.addCustom === F)
|
|
56
133
|
{
|
|
57
|
-
|
|
134
|
+
opts.addCustom.call(this, this.customFn);
|
|
58
135
|
}
|
|
59
136
|
}
|
|
60
137
|
|
|
61
|
-
|
|
62
|
-
const classname = () => (addNew ? 'new ' : '') + what.constructor.name;
|
|
63
|
-
const construct = val => `${classname()}(${val})`;
|
|
64
|
-
const reconstruct = val => construct(stringify(val,recurse,addNew));
|
|
65
|
-
const arrayish = vals => reconstruct(Array.from(vals));
|
|
138
|
+
}
|
|
66
139
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Stringify a JS value
|
|
142
|
+
* @param {*} what - Value to stringify
|
|
143
|
+
* @returns {string} A friendly representation of `what`
|
|
144
|
+
*/
|
|
145
|
+
stringify(what, curd=0)
|
|
146
|
+
{
|
|
147
|
+
if (this.seenObjects.has(what))
|
|
148
|
+
{ // Cached string for this subject already found.
|
|
149
|
+
return this.seenObjects.get(what);
|
|
70
150
|
}
|
|
151
|
+
|
|
152
|
+
const recurse = curd < this.maxDepth;
|
|
153
|
+
const addNew = curd < this.newDepth;
|
|
154
|
+
const whatType = typeof what;
|
|
155
|
+
const strFor = (str) => this._stringFor(what, str);
|
|
71
156
|
|
|
72
|
-
|
|
73
|
-
{
|
|
74
|
-
|
|
157
|
+
for (const test of this.customFn)
|
|
158
|
+
{ // If there are custom extensions, we check them first.
|
|
159
|
+
const ret = test.call(this, what, curd);
|
|
160
|
+
if (typeof ret === S)
|
|
161
|
+
{ // The extension processed the item.
|
|
162
|
+
return strFor(ret);
|
|
163
|
+
}
|
|
75
164
|
}
|
|
76
|
-
|
|
77
|
-
|
|
165
|
+
|
|
166
|
+
// A few types we simply stringify right now.
|
|
167
|
+
if (this.strTypes.includes(whatType))
|
|
78
168
|
{
|
|
79
|
-
return
|
|
169
|
+
return strFor(what.toString());
|
|
80
170
|
}
|
|
81
171
|
|
|
82
|
-
if (what
|
|
83
|
-
{
|
|
84
|
-
return
|
|
172
|
+
if (!this.options.fnString && typeof what === F)
|
|
173
|
+
{ // Simple format for functions
|
|
174
|
+
return strFor(what.name+'()');
|
|
85
175
|
}
|
|
86
176
|
|
|
87
|
-
if (
|
|
88
|
-
{ //
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
out += ']';
|
|
95
|
-
}
|
|
96
|
-
else
|
|
97
|
-
{ // Stringify a plain object.
|
|
98
|
-
out = '{';
|
|
99
|
-
function add(key, pre='')
|
|
177
|
+
if (isObj(what))
|
|
178
|
+
{ // We support a few kinds of objects.
|
|
179
|
+
|
|
180
|
+
// Any class instance that we can simply call `toString()` on, let's do that.
|
|
181
|
+
for (const aClass of this.strClass)
|
|
182
|
+
{
|
|
183
|
+
if (what instanceof aClass)
|
|
100
184
|
{
|
|
101
|
-
|
|
185
|
+
return strFor(what.toString());
|
|
102
186
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// A few formatting helpers used below.
|
|
190
|
+
const nameFor = (name=what.constructor.name) =>
|
|
191
|
+
(addNew ? 'new ' : '') + strFor(name);
|
|
192
|
+
|
|
193
|
+
const container = (content, ps='[', pe=']') =>
|
|
194
|
+
{
|
|
195
|
+
let cstr = nameFor();
|
|
196
|
+
if (recurse)
|
|
197
|
+
{
|
|
198
|
+
cstr += ps;
|
|
199
|
+
if (!Array.isArray(content))
|
|
109
200
|
{
|
|
110
|
-
|
|
201
|
+
content = Array.from(content);
|
|
111
202
|
}
|
|
203
|
+
cstr += (content
|
|
204
|
+
.map(item => this.stringify(item, curd+1))
|
|
205
|
+
.join(','));
|
|
206
|
+
cstr += pe;
|
|
207
|
+
}
|
|
208
|
+
return cstr;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (isTypedArray(what))
|
|
212
|
+
{ // This one is pretty simple.
|
|
213
|
+
if (this.options.typedContent)
|
|
214
|
+
{
|
|
215
|
+
return container(what.toString());
|
|
112
216
|
}
|
|
113
|
-
|
|
217
|
+
return nameFor();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (what instanceof Map)
|
|
221
|
+
{
|
|
222
|
+
return container(what.entries());
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (what instanceof Set)
|
|
226
|
+
{
|
|
227
|
+
return container(what.values());
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (isArray(what))
|
|
231
|
+
{
|
|
232
|
+
return container(what);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!this.options.errString && what instanceof Error)
|
|
236
|
+
{
|
|
237
|
+
return nameFor(what.name)+'('+JSON.stringify(what.message)+')';
|
|
114
238
|
}
|
|
115
|
-
|
|
239
|
+
|
|
240
|
+
// If we reached here, it's another kind of object entirely.
|
|
241
|
+
return container(Object.keys(what), '{', '}');
|
|
242
|
+
|
|
243
|
+
} // if isObj
|
|
244
|
+
|
|
245
|
+
// If we reached here, there's no special methods, use JSON.
|
|
246
|
+
return JSON.stringify(what);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
_stringFor(obj, str)
|
|
250
|
+
{
|
|
251
|
+
let count = 0;
|
|
252
|
+
if (this.seenDescrip.has(str))
|
|
253
|
+
{
|
|
254
|
+
count = this.seenDescrip.get(str);
|
|
116
255
|
}
|
|
256
|
+
this.seenDescrip.set(str, ++count);
|
|
257
|
+
str += '#' + count.toString(16).padStart(3, '0');
|
|
258
|
+
this.seenObjects.set(obj, str);
|
|
259
|
+
return str;
|
|
260
|
+
}
|
|
117
261
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
262
|
+
static addCustoms(...testFns)
|
|
263
|
+
{
|
|
264
|
+
for (let fn of testFns)
|
|
265
|
+
{
|
|
266
|
+
if (typeof fn === F)
|
|
267
|
+
{
|
|
268
|
+
CUSTOM.push(fn);
|
|
269
|
+
}
|
|
270
|
+
else
|
|
271
|
+
{
|
|
272
|
+
console.debug({fn, testFns});
|
|
273
|
+
throw new TypeError("Invalid custom stringify function");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
123
277
|
|
|
124
|
-
|
|
125
|
-
def(stringify, '$extend',
|
|
126
|
-
function(func, registration=false)
|
|
127
|
-
{
|
|
128
|
-
if (typeof func === TYPES.F)
|
|
278
|
+
static registerCustoms(registerFn)
|
|
129
279
|
{
|
|
130
|
-
if (
|
|
131
|
-
{
|
|
132
|
-
|
|
280
|
+
if (typeof registerFn === F)
|
|
281
|
+
{
|
|
282
|
+
registerFn.call(this, this);
|
|
133
283
|
}
|
|
134
|
-
else
|
|
135
|
-
{
|
|
136
|
-
|
|
284
|
+
else
|
|
285
|
+
{
|
|
286
|
+
console.debug({registerFn});
|
|
287
|
+
throw new TypeError("Invalid custom registration function");
|
|
137
288
|
}
|
|
138
289
|
}
|
|
139
|
-
|
|
290
|
+
|
|
291
|
+
} // Stringify
|
|
292
|
+
|
|
293
|
+
// Add some static properties for registerCustoms to use
|
|
294
|
+
def(Stringify)
|
|
295
|
+
('strClass', {value: TOSTRING_INSTANCES})
|
|
296
|
+
('strTypes', {value: TOSTRING_TYPES})
|
|
297
|
+
('customFn', {value: CUSTOM})
|
|
298
|
+
('DEFAULTS', {value: DEF})
|
|
140
299
|
|
|
141
300
|
// Export it.
|
|
142
|
-
module.exports = stringify;
|
|
301
|
+
module.exports = {stringify, Stringify};
|