@lumjs/core 1.14.0 → 1.15.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/arrays.js +290 -55
- package/lib/index.js +12 -14
- package/package.json +1 -1
package/lib/arrays.js
CHANGED
|
@@ -1,82 +1,314 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Array helper functions.
|
|
2
|
+
* Array (and other list objects) helper functions.
|
|
3
3
|
* @module @lumjs/core/arrays
|
|
4
4
|
*/
|
|
5
|
+
const {F,S} = require('./types/js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A wrapper class to abstract functionality of various kinds of lists.
|
|
9
|
+
*
|
|
10
|
+
* Supports `Array`, `Set`, and `Map` explicitly.
|
|
11
|
+
* Other classes may be supported. YMMV.
|
|
12
|
+
*
|
|
13
|
+
* @alias module:@lumjs/core/arrays.List
|
|
14
|
+
*/
|
|
15
|
+
class List
|
|
16
|
+
{
|
|
17
|
+
/**
|
|
18
|
+
* A list of known closure properties.
|
|
19
|
+
*
|
|
20
|
+
* Corresponding `wrap_${closure}` methods will be used to generate
|
|
21
|
+
* the closure properties.
|
|
22
|
+
*/
|
|
23
|
+
static get KNOWN_CLOSURES()
|
|
24
|
+
{
|
|
25
|
+
return (Object.getOwnPropertyNames(this.prototype)
|
|
26
|
+
.filter(name => name.startsWith('wrap_')));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build a new List wrapper.
|
|
31
|
+
*
|
|
32
|
+
* @param {object} obj - The list object.
|
|
33
|
+
*
|
|
34
|
+
* Typically an `Array`, `Set`, `Map`, or something similar.
|
|
35
|
+
* Not all of types will support all features. YMMV.
|
|
36
|
+
*
|
|
37
|
+
* @param {object} [opts] Options for advanced use.
|
|
38
|
+
*
|
|
39
|
+
* @param {bool} [opts.allowRaw=false] Attempt to support raw objects?
|
|
40
|
+
*
|
|
41
|
+
* Treat regular Javascript objects as lists, as they are a kind of
|
|
42
|
+
* map after all. This may lead to unusual results. YMMV.
|
|
43
|
+
*
|
|
44
|
+
* @param {mixed} [opts.closures=true] Create closure methods?
|
|
45
|
+
*
|
|
46
|
+
* If `true` or `"*"` we will create all known closure properties.
|
|
47
|
+
*
|
|
48
|
+
* If an `Array` it's the list of closure names we want to create.
|
|
49
|
+
* Possible values can be found in `List.KNOWN_CLOSURES`.
|
|
50
|
+
*
|
|
51
|
+
* If it's a `string` it's treated as a whitespace separated list
|
|
52
|
+
* of closure names and split into an `Array`.
|
|
53
|
+
*
|
|
54
|
+
* If `false` we won't create any closure properties.
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
57
|
+
constructor(obj, opts={})
|
|
58
|
+
{
|
|
59
|
+
this.obj = obj;
|
|
60
|
+
this.opts = opts;
|
|
61
|
+
this.allowRaw = opts.allowRaw ?? false;
|
|
62
|
+
this.setupClosures(opts.closures ?? true);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setupClosures(closures)
|
|
66
|
+
{
|
|
67
|
+
if (!closures)
|
|
68
|
+
{ // Nothing to do.
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (closures === true || closures === '*')
|
|
73
|
+
{ // Setup all closures.
|
|
74
|
+
closures = this.constructor.KNOWN_CLOSURES;
|
|
75
|
+
}
|
|
76
|
+
else if (typeof closures === S)
|
|
77
|
+
{
|
|
78
|
+
closures = closures.trim().split(/\s+/);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (Array.isArray(closures))
|
|
82
|
+
{
|
|
83
|
+
for (const closure of closures)
|
|
84
|
+
{
|
|
85
|
+
const meth = 'wrap_'+closure;
|
|
86
|
+
if (typeof this[meth] === F)
|
|
87
|
+
{ // Create the detail property.
|
|
88
|
+
this[closure] = this[meth]();
|
|
89
|
+
}
|
|
90
|
+
else
|
|
91
|
+
{
|
|
92
|
+
console.error({closure, closures, list: this});
|
|
93
|
+
throw new Error("Unsupported closure");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else
|
|
98
|
+
{
|
|
99
|
+
console.error({closures, list: this});
|
|
100
|
+
throw new TypeError("Invalid closures value");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
unsupported(obj, forClosure, info, retVal=false)
|
|
105
|
+
{
|
|
106
|
+
console.error("Unsupported object", {obj, forClosure, info, list: this});
|
|
107
|
+
return () => retVal;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Return a closure that returns if an item is in a list object.
|
|
112
|
+
*
|
|
113
|
+
* @param {object} obj - The list object the closure will be for.
|
|
114
|
+
*
|
|
115
|
+
* May be an `Array`, `Set`, `Map`, or `TypedArray` or any object
|
|
116
|
+
* that has either an `includes()` or `has()` method.
|
|
117
|
+
*
|
|
118
|
+
* @returns {function}
|
|
119
|
+
* @throws {TypeError}
|
|
120
|
+
*/
|
|
121
|
+
wrap_contains(obj=this.obj, allowRaw=this.allowRaw)
|
|
122
|
+
{
|
|
123
|
+
if (typeof obj.includes === F)
|
|
124
|
+
{ // Array and TypedArray have includes()
|
|
125
|
+
return item => obj.includes(item);
|
|
126
|
+
}
|
|
127
|
+
else if (typeof obj.has === F)
|
|
128
|
+
{ // Set and Map have has()
|
|
129
|
+
return item => obj.has(item);
|
|
130
|
+
}
|
|
131
|
+
else if (allowRaw)
|
|
132
|
+
{ // A fallback to raw object search.
|
|
133
|
+
return item => (typeof obj[item] !== undefined);
|
|
134
|
+
}
|
|
135
|
+
else
|
|
136
|
+
{ // Nope.
|
|
137
|
+
return this.unsupported(obj, 'contains', {allowRaw});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Return a closure that removes an item from a list.
|
|
143
|
+
*
|
|
144
|
+
* @param {object} obj - The list object the closure will be for.
|
|
145
|
+
*
|
|
146
|
+
* May be an `Array`, `Set`, `Map`, or anything with a `delete()` method.
|
|
147
|
+
*
|
|
148
|
+
* @returns {function}
|
|
149
|
+
* @throws {TypeError}
|
|
150
|
+
*/
|
|
151
|
+
wrap_remove(obj=this.obj, allowRaw=this.allowRaw)
|
|
152
|
+
{
|
|
153
|
+
let closure;
|
|
154
|
+
if (Array.isArray(obj))
|
|
155
|
+
{ // Arrays have no easy method to do this.
|
|
156
|
+
closure = function(item)
|
|
157
|
+
{
|
|
158
|
+
let removed = 0;
|
|
159
|
+
let index = obj.indexOf(item);
|
|
160
|
+
while (index !== -1)
|
|
161
|
+
{
|
|
162
|
+
obj.splice(index, 1);
|
|
163
|
+
removed++;
|
|
164
|
+
index = obj.indexOf(item, index);
|
|
165
|
+
}
|
|
166
|
+
return removed;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else if (typeof obj.delete === F)
|
|
170
|
+
{ // Use the delete() method.
|
|
171
|
+
closure = item => (obj.delete(item) ? 1 : 0);
|
|
172
|
+
}
|
|
173
|
+
else if (allowRaw)
|
|
174
|
+
{ // Fallback to removing the property.
|
|
175
|
+
closure = function(item)
|
|
176
|
+
{
|
|
177
|
+
if (obj[item] === undefined)
|
|
178
|
+
{
|
|
179
|
+
return 0;
|
|
180
|
+
}
|
|
181
|
+
else
|
|
182
|
+
{
|
|
183
|
+
return (delete obj[item] ? 1 : 0);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else
|
|
188
|
+
{ // Nada.
|
|
189
|
+
closure = this.unsupported(obj, 'remove', {allowRaw}, 0);
|
|
190
|
+
}
|
|
191
|
+
return closure;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* See if our list object contains *any* of the specified items.
|
|
196
|
+
*
|
|
197
|
+
* @param {...any} items
|
|
198
|
+
* @returns {boolean}
|
|
199
|
+
*/
|
|
200
|
+
containsAny(...items)
|
|
201
|
+
{
|
|
202
|
+
for (const item of items)
|
|
203
|
+
{
|
|
204
|
+
if (this.contains(item))
|
|
205
|
+
{ // Item found.
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// No items found.
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* See if our list contains *all* of the specified items.
|
|
216
|
+
*
|
|
217
|
+
* @param {...any} items
|
|
218
|
+
* @returns {boolean}
|
|
219
|
+
*/
|
|
220
|
+
containsAll(...items)
|
|
221
|
+
{
|
|
222
|
+
for (const item of items)
|
|
223
|
+
{
|
|
224
|
+
if (!this.contains(item))
|
|
225
|
+
{ // An item was missing.
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// All items found.
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Remove items from our list object.
|
|
235
|
+
*
|
|
236
|
+
* Passed any number of items, it will see if any of those items are
|
|
237
|
+
* found in the array, and if they are, will remove them from the array.
|
|
238
|
+
*
|
|
239
|
+
* @param {...any} items
|
|
240
|
+
* @returns {number} Number of items actually removed.
|
|
241
|
+
*/
|
|
242
|
+
removeAll(...items)
|
|
243
|
+
{
|
|
244
|
+
let removed = 0;
|
|
245
|
+
for (const item of items)
|
|
246
|
+
{
|
|
247
|
+
removed += this.remove(item);
|
|
248
|
+
}
|
|
249
|
+
return removed;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Return a List instance for the passed object.
|
|
254
|
+
*
|
|
255
|
+
* If the object is already a `List` it is returned as is.
|
|
256
|
+
*
|
|
257
|
+
* @param {object} obj - The list object.
|
|
258
|
+
* @param {object} [opts] - Passed to constructor.
|
|
259
|
+
*/
|
|
260
|
+
static for(obj, opts)
|
|
261
|
+
{
|
|
262
|
+
return (obj instanceof this) ? obj : new this(obj, opts);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const CONTAINS_OPTS = {closures: ['contains']};
|
|
5
267
|
|
|
6
268
|
/**
|
|
7
|
-
* See if an
|
|
8
|
-
*
|
|
269
|
+
* See if an list object contains *any* of the specified items.
|
|
270
|
+
*
|
|
271
|
+
* @param {object} list - A `List` or object for `new List(list)`;
|
|
9
272
|
* @param {...any} items
|
|
10
273
|
* @returns {boolean}
|
|
11
274
|
* @alias module:@lumjs/core/arrays.containsAny
|
|
12
275
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (array.includes(item))
|
|
18
|
-
{ // Item found.
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// No items found.
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
exports.containsAny = containsAny;
|
|
276
|
+
function containsAny(list, ...items)
|
|
277
|
+
{
|
|
278
|
+
return List.for(list, CONTAINS_OPTS).containsAny(...items);
|
|
279
|
+
}
|
|
28
280
|
|
|
29
281
|
/**
|
|
30
|
-
* See if
|
|
31
|
-
*
|
|
282
|
+
* See if a list contains *all* of the specified items.
|
|
283
|
+
*
|
|
284
|
+
* @param {object} list - A `List` or object for `new List(list)`;
|
|
32
285
|
* @param {...any} items
|
|
33
286
|
* @returns {boolean}
|
|
34
287
|
* @alias module:@lumjs/core/arrays.containsAll
|
|
35
288
|
*/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// All items found.
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
exports.containsAll = containsAll;
|
|
51
|
-
|
|
289
|
+
function containsAll(list, ...items)
|
|
290
|
+
{
|
|
291
|
+
return List.for(list, CONTAINS_OPTS).containsAll(...items);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const REMOVE_OPTS = {closures: ['remove']};
|
|
295
|
+
|
|
52
296
|
/**
|
|
53
|
-
* Remove items from
|
|
297
|
+
* Remove items from a list object.
|
|
54
298
|
*
|
|
55
299
|
* Passed any number of items, it will see if any of those items are
|
|
56
300
|
* found in the array, and if they are, will remove them from the array.
|
|
57
301
|
*
|
|
58
|
-
* @param {
|
|
302
|
+
* @param {object} list - A `List` or object for `new List(list)`;
|
|
59
303
|
* @param {...any} items
|
|
60
304
|
* @returns {number} Number of items actually removed.
|
|
61
305
|
* @alias module:@lumjs/core/arrays.removeItems
|
|
62
306
|
*/
|
|
63
|
-
function removeItems(
|
|
307
|
+
function removeItems(list, ...items)
|
|
64
308
|
{
|
|
65
|
-
|
|
66
|
-
for (const item of items)
|
|
67
|
-
{
|
|
68
|
-
const index = array.indexOf(item);
|
|
69
|
-
if (index !== -1)
|
|
70
|
-
{
|
|
71
|
-
array.splice(index, 1);
|
|
72
|
-
removed++;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return removed;
|
|
309
|
+
return List.for(list, REMOVE_OPTS).removeAll(...items);
|
|
76
310
|
}
|
|
77
311
|
|
|
78
|
-
exports.removeItems = removeItems;
|
|
79
|
-
|
|
80
312
|
/**
|
|
81
313
|
* Return a Powerset of values in the array.
|
|
82
314
|
* @param {Array} array The array to make the powerset from.
|
|
@@ -99,8 +331,6 @@ function powerset(array)
|
|
|
99
331
|
return ps;
|
|
100
332
|
}
|
|
101
333
|
|
|
102
|
-
exports.powerset = powerset;
|
|
103
|
-
|
|
104
334
|
/**
|
|
105
335
|
* Get a random element from an array.
|
|
106
336
|
* @param {Array} array The array to get an item from.
|
|
@@ -112,4 +342,9 @@ function random(array)
|
|
|
112
342
|
return array[Math.floor(Math.random()*array.length)];
|
|
113
343
|
}
|
|
114
344
|
|
|
115
|
-
exports
|
|
345
|
+
module.exports = exports =
|
|
346
|
+
{
|
|
347
|
+
List, containsAny, containsAll,
|
|
348
|
+
removeItems, powerset, random,
|
|
349
|
+
}
|
|
350
|
+
|
package/lib/index.js
CHANGED
|
@@ -33,47 +33,44 @@ const def = types.def;
|
|
|
33
33
|
*/
|
|
34
34
|
const lazy = types.lazy;
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def(exports, 'types', types, E);
|
|
40
|
-
def(exports, 'def', def, E);
|
|
41
|
-
def(exports, 'lazy', lazy, E);
|
|
36
|
+
def(exports, 'types', types);
|
|
37
|
+
def(exports, 'def', def);
|
|
38
|
+
def(exports, 'lazy', lazy);
|
|
42
39
|
|
|
43
40
|
/**
|
|
44
41
|
* Array utility functions «Lazy»
|
|
45
42
|
* @name module:@lumjs/core.arrays
|
|
46
43
|
* @type {module:@lumjs/core/arrays}
|
|
47
44
|
*/
|
|
48
|
-
lazy(exports, 'arrays', () => require('./arrays')
|
|
45
|
+
lazy(exports, 'arrays', () => require('./arrays'));
|
|
49
46
|
|
|
50
47
|
/**
|
|
51
48
|
* Information about the JS context we're running in
|
|
52
49
|
* @name module:@lumjs/core.context
|
|
53
50
|
* @type {module:@lumjs/core/context}
|
|
54
51
|
*/
|
|
55
|
-
def(exports, 'context', require('./context')
|
|
52
|
+
def(exports, 'context', require('./context'));
|
|
56
53
|
|
|
57
54
|
/**
|
|
58
55
|
* Functions for working with strings and locales «Lazy»
|
|
59
56
|
* @name module:@lumjs/core.strings
|
|
60
57
|
* @type {module:@lumjs/core/strings}
|
|
61
58
|
*/
|
|
62
|
-
lazy(exports, 'strings', () => require('./strings')
|
|
59
|
+
lazy(exports, 'strings', () => require('./strings'));
|
|
63
60
|
|
|
64
61
|
/**
|
|
65
62
|
* Functions for working with binary flags «Lazy»
|
|
66
63
|
* @name module:@lumjs/core.flags
|
|
67
64
|
* @type {module:@lumjs/core/flags}
|
|
68
65
|
*/
|
|
69
|
-
lazy(exports, 'flags', () => require('./flags')
|
|
66
|
+
lazy(exports, 'flags', () => require('./flags'));
|
|
70
67
|
|
|
71
68
|
/**
|
|
72
69
|
* Functions for manipulating objects «Lazy»
|
|
73
70
|
* @name module:@lumjs/core.obj
|
|
74
71
|
* @type {module:@lumjs/core/obj}
|
|
75
72
|
*/
|
|
76
|
-
lazy(exports, 'obj', () => require('./obj')
|
|
73
|
+
lazy(exports, 'obj', () => require('./obj'));
|
|
77
74
|
|
|
78
75
|
/**
|
|
79
76
|
* Functions for getting values and properties with fallback defaults «Lazy»
|
|
@@ -88,7 +85,7 @@ function from(submod, ...libs)
|
|
|
88
85
|
{
|
|
89
86
|
for (const lib of libs)
|
|
90
87
|
{
|
|
91
|
-
def(exports, lib, submod[lib]
|
|
88
|
+
def(exports, lib, submod[lib]);
|
|
92
89
|
}
|
|
93
90
|
}
|
|
94
91
|
|
|
@@ -133,7 +130,7 @@ from(meta, 'stacktrace', 'AbstractClass', 'Functions', 'NYI');
|
|
|
133
130
|
* @function
|
|
134
131
|
* @see module:@lumjs/core/enum
|
|
135
132
|
*/
|
|
136
|
-
lazy(exports, 'Enum', () => require('./enum')
|
|
133
|
+
lazy(exports, 'Enum', () => require('./enum'));
|
|
137
134
|
|
|
138
135
|
/**
|
|
139
136
|
* Make an object support the *Observable* API «Lazy»
|
|
@@ -141,4 +138,5 @@ lazy(exports, 'Enum', () => require('./enum'), E);
|
|
|
141
138
|
* @function
|
|
142
139
|
* @see module:@lumjs/core/observable
|
|
143
140
|
*/
|
|
144
|
-
lazy(exports, 'observable', () => require('./observable')
|
|
141
|
+
lazy(exports, 'observable', () => require('./observable'));
|
|
142
|
+
|