@wizhut_tech/wizjs 0.1.5 → 0.1.7
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/.github/workflows/release.yml +59 -0
- package/CLAUDE.md +11 -9
- package/__tests__/lang_arrays.test.js +44 -2
- package/__tests__/lang_checks.test.js +63 -2
- package/__tests__/lang_collections.test.js +90 -0
- package/__tests__/lang_functools.test.js +123 -2
- package/__tests__/lang_itertools.test.js +210 -3
- package/__tests__/lang_objects.test.js +71 -0
- package/__tests__/math_numbers.test.js +40 -1
- package/docs/lang_arrays.md +4 -1
- package/docs/lang_checks.md +4 -0
- package/docs/lang_collections.md +24 -0
- package/docs/lang_functools.md +8 -1
- package/docs/lang_itertools.md +20 -1
- package/docs/lang_objects.md +5 -0
- package/docs/math_numbers.md +4 -1
- package/index.js +5 -1
- package/package.json +2 -2
- package/readme.md +6 -2
- package/src/lang/arrays.js +74 -3
- package/src/lang/checks.js +42 -2
- package/src/lang/collections.js +191 -0
- package/src/lang/functools.js +135 -2
- package/src/lang/itertools.js +511 -8
- package/src/lang/objects.js +123 -0
- package/src/math/numbers.js +59 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const t = require('tap');
|
|
2
2
|
|
|
3
|
-
const { count, repeat } = require('../src/lang/itertools.js');
|
|
3
|
+
const { count, repeat, cycle, chain, take, zip, range, enumerate, islice, takewhile, dropwhile, filterfalse, zip_longest, product, groupby, starmap, batched, pairwise, compress } = require('../src/lang/itertools.js');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
t.test('itertools/count', (t) => {
|
|
@@ -25,7 +25,7 @@ t.test('itertools/repeat', (t) => {
|
|
|
25
25
|
// single repeat
|
|
26
26
|
const repeatGen = repeat(10, 1);
|
|
27
27
|
t.equal(repeatGen.next().value, 10);
|
|
28
|
-
t.equal(repeatGen.
|
|
28
|
+
t.equal(repeatGen.next().done, true);
|
|
29
29
|
|
|
30
30
|
// eternal repeat
|
|
31
31
|
const repeatGenTwo = repeat(15, 0);
|
|
@@ -39,4 +39,211 @@ t.test('itertools/repeat', (t) => {
|
|
|
39
39
|
t.equal(repeatGenThree.return().done, true);
|
|
40
40
|
|
|
41
41
|
t.end();
|
|
42
|
-
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
t.test('itertools/cycle', (t) => {
|
|
46
|
+
const cycled = cycle([1, 2, 3]);
|
|
47
|
+
t.equal(cycled.next().value, 1);
|
|
48
|
+
t.equal(cycled.next().value, 2);
|
|
49
|
+
t.equal(cycled.next().value, 3);
|
|
50
|
+
t.equal(cycled.next().value, 1);
|
|
51
|
+
t.equal(cycled.next().value, 2);
|
|
52
|
+
t.equal(cycled.next().value, 3);
|
|
53
|
+
t.equal(cycled.next().value, 1);
|
|
54
|
+
t.equal(cycled.return().done, true);
|
|
55
|
+
|
|
56
|
+
const fromGen = cycle((function* () { yield 'a'; yield 'b'; })());
|
|
57
|
+
t.equal(fromGen.next().value, 'a');
|
|
58
|
+
t.equal(fromGen.next().value, 'b');
|
|
59
|
+
t.equal(fromGen.next().value, 'a');
|
|
60
|
+
t.equal(fromGen.return().done, true);
|
|
61
|
+
|
|
62
|
+
t.equal(cycle(null).next().done, true);
|
|
63
|
+
t.equal(cycle([]).next().done, true);
|
|
64
|
+
|
|
65
|
+
t.end();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
t.test('itertools/chain', (t) => {
|
|
70
|
+
t.match([...chain([1, 2], [3, 4])], [1, 2, 3, 4]);
|
|
71
|
+
t.match([...chain([1], [], [2, 3])], [1, 2, 3]);
|
|
72
|
+
t.match([...chain(null, [1], undefined, [2])], [1, 2]);
|
|
73
|
+
t.match([...chain()], []);
|
|
74
|
+
|
|
75
|
+
const gen = (function* () { yield 9; })();
|
|
76
|
+
t.match([...chain([1], gen)], [1, 9]);
|
|
77
|
+
|
|
78
|
+
t.end();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
t.test('itertools/take', (t) => {
|
|
83
|
+
t.match(take(3, count(0)), [0, 1, 2]);
|
|
84
|
+
t.match(take(2, [10, 20, 30, 40]), [10, 20]);
|
|
85
|
+
t.match(take(10, [1, 2]), [1, 2]);
|
|
86
|
+
t.match(take(0, [1, 2, 3]), []);
|
|
87
|
+
t.match(take(-1, [1, 2, 3]), []);
|
|
88
|
+
t.match(take(3, null), []);
|
|
89
|
+
t.match(take(4, cycle([1, 2])), [1, 2, 1, 2]);
|
|
90
|
+
t.end();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
t.test('itertools/zip', (t) => {
|
|
95
|
+
t.match([...zip([1, 2, 3], ['a', 'b', 'c'])], [[1, 'a'], [2, 'b'], [3, 'c']]);
|
|
96
|
+
t.match([...zip([1, 2, 3], ['a', 'b'])], [[1, 'a'], [2, 'b']]);
|
|
97
|
+
t.match([...zip([1], ['a'], [true, false])], [[1, 'a', true]]);
|
|
98
|
+
t.match([...zip()], []);
|
|
99
|
+
t.match([...zip(null, [1, 2])], []);
|
|
100
|
+
t.match([...zip(count(0), ['x', 'y'])], [[0, 'x'], [1, 'y']]);
|
|
101
|
+
t.end();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
t.test('itertools/range', (t) => {
|
|
106
|
+
t.match([...range(5)], [0, 1, 2, 3, 4]);
|
|
107
|
+
t.match([...range(1, 5)], [1, 2, 3, 4]);
|
|
108
|
+
t.match([...range(0, 10, 2)], [0, 2, 4, 6, 8]);
|
|
109
|
+
t.match([...range(5, 0, -1)], [5, 4, 3, 2, 1]);
|
|
110
|
+
t.match([...range(0, 1, 0.5)], [0, 0.5]);
|
|
111
|
+
t.match([...range(0, 0)], []);
|
|
112
|
+
t.match([...range(-3)], []);
|
|
113
|
+
t.match([...range(0, 10, 0)], []);
|
|
114
|
+
t.match([...range()], []);
|
|
115
|
+
t.end();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
t.test('itertools/enumerate', (t) => {
|
|
120
|
+
t.match([...enumerate(['a', 'b', 'c'])], [[0, 'a'], [1, 'b'], [2, 'c']]);
|
|
121
|
+
t.match([...enumerate(['a', 'b'], 10)], [[10, 'a'], [11, 'b']]);
|
|
122
|
+
t.match([...enumerate([])], []);
|
|
123
|
+
t.match([...enumerate(null)], []);
|
|
124
|
+
t.end();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
t.test('itertools/islice', (t) => {
|
|
129
|
+
t.match([...islice('ABCDEFG', 2)], ['A', 'B']);
|
|
130
|
+
t.match([...islice('ABCDEFG', 2, 4)], ['C', 'D']);
|
|
131
|
+
t.match([...islice('ABCDEFG', 2, null)], ['C', 'D', 'E', 'F', 'G']);
|
|
132
|
+
t.match([...islice('ABCDEFG', 0, null, 2)], ['A', 'C', 'E', 'G']);
|
|
133
|
+
t.match([...islice('ABCDEFG', 2, 6, 2)], ['C', 'E']);
|
|
134
|
+
t.match([...islice('ABC', null, 2)], ['A', 'B']);
|
|
135
|
+
t.match([...islice('ABCD', 0, 4, null)], ['A', 'B', 'C', 'D']);
|
|
136
|
+
t.match([...islice([1, 2, 3], 0)], []);
|
|
137
|
+
t.match([...islice([1, 2, 3], 2, 2)], []);
|
|
138
|
+
t.match([...islice([1, 2, 3], 0, 3, 0)], []);
|
|
139
|
+
t.match([...islice(null, 3)], []);
|
|
140
|
+
t.end();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
t.test('itertools/takewhile', (t) => {
|
|
145
|
+
t.match([...takewhile((x) => x < 5, [1, 4, 6, 4, 1])], [1, 4]);
|
|
146
|
+
t.match([...takewhile((x) => x < 0, [1, 2])], []);
|
|
147
|
+
t.match([...takewhile((x) => x < 9, [1, 2])], [1, 2]);
|
|
148
|
+
t.match([...takewhile((x) => x, null)], []);
|
|
149
|
+
t.match([...takewhile(null, [1, 2])], []);
|
|
150
|
+
t.end();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
t.test('itertools/dropwhile', (t) => {
|
|
155
|
+
t.match([...dropwhile((x) => x < 5, [1, 4, 6, 4, 1])], [6, 4, 1]);
|
|
156
|
+
t.match([...dropwhile((x) => x < 0, [1, 2])], [1, 2]);
|
|
157
|
+
t.match([...dropwhile((x) => x < 9, [1, 2])], []);
|
|
158
|
+
t.match([...dropwhile((x) => x, null)], []);
|
|
159
|
+
t.match([...dropwhile(null, [1, 2])], []);
|
|
160
|
+
t.end();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
t.test('itertools/filterfalse', (t) => {
|
|
165
|
+
t.match([...filterfalse((x) => x % 2, range(10))], [0, 2, 4, 6, 8]);
|
|
166
|
+
t.match([...filterfalse((x) => x, [0, 1, false, 2, ''])], [0, false, '']);
|
|
167
|
+
t.match([...filterfalse((x) => x, null)], []);
|
|
168
|
+
t.match([...filterfalse(null, [1, 2])], []);
|
|
169
|
+
t.end();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
t.test('itertools/zip_longest', (t) => {
|
|
174
|
+
t.match([...zip_longest([1, 2, 3], ['a', 'b'])], [[1, 'a'], [2, 'b'], [3, undefined]]);
|
|
175
|
+
t.match([...zip_longest([1, 2], ['a', 'b', 'c'], { fillvalue: '-' })], [[1, 'a'], [2, 'b'], ['-', 'c']]);
|
|
176
|
+
t.match([...zip_longest()], []);
|
|
177
|
+
t.match([...zip_longest(null, [1])], []);
|
|
178
|
+
t.match([...zip_longest([], [1, 2], { fillvalue: 0 })], [[0, 1], [0, 2]]);
|
|
179
|
+
t.end();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
t.test('itertools/product', (t) => {
|
|
184
|
+
t.match([...product()], [[]]);
|
|
185
|
+
t.match([...product('AB', [1, 2])], [['A', 1], ['A', 2], ['B', 1], ['B', 2]]);
|
|
186
|
+
t.match([...product([1, 2], [3], [4, 5])], [[1, 3, 4], [1, 3, 5], [2, 3, 4], [2, 3, 5]]);
|
|
187
|
+
t.match([...product([1, 2], [])], []);
|
|
188
|
+
t.match([...product(null, [1])], []);
|
|
189
|
+
t.end();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
t.test('itertools/groupby', (t) => {
|
|
194
|
+
t.match([...groupby('AAAABBBCCDAABBB')], [
|
|
195
|
+
['A', ['A', 'A', 'A', 'A']],
|
|
196
|
+
['B', ['B', 'B', 'B']],
|
|
197
|
+
['C', ['C', 'C']],
|
|
198
|
+
['D', ['D']],
|
|
199
|
+
['A', ['A', 'A']],
|
|
200
|
+
['B', ['B', 'B', 'B']]
|
|
201
|
+
]);
|
|
202
|
+
t.match([...groupby([1, 2, 3, 4], (x) => x % 2)], [
|
|
203
|
+
[1, [1]],
|
|
204
|
+
[0, [2]],
|
|
205
|
+
[1, [3]],
|
|
206
|
+
[0, [4]]
|
|
207
|
+
]);
|
|
208
|
+
t.match([...groupby([])], []);
|
|
209
|
+
t.match([...groupby(null)], []);
|
|
210
|
+
t.match([...groupby([NaN, NaN, 1])], [[NaN, [NaN, NaN]], [1, [1]]]);
|
|
211
|
+
t.end();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
t.test('itertools/starmap', (t) => {
|
|
216
|
+
t.match([...starmap(Math.pow, [[2, 5], [3, 2], [10, 3]])], [32, 9, 1000]);
|
|
217
|
+
t.match([...starmap((a, b) => a + b, zip([1, 2], [3, 4]))], [4, 6]);
|
|
218
|
+
t.match([...starmap(Math.max, null)], []);
|
|
219
|
+
t.match([...starmap(null, [[1]])], []);
|
|
220
|
+
t.end();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
t.test('itertools/batched', (t) => {
|
|
225
|
+
t.match([...batched('ABCDEFG', 3)], [['A', 'B', 'C'], ['D', 'E', 'F'], ['G']]);
|
|
226
|
+
t.match([...batched([1, 2, 3], 3)], [[1, 2, 3]]);
|
|
227
|
+
t.match([...batched([1, 2, 3], 0)], []);
|
|
228
|
+
t.match([...batched(null, 2)], []);
|
|
229
|
+
t.match([...batched([], 2)], []);
|
|
230
|
+
t.end();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
t.test('itertools/pairwise', (t) => {
|
|
235
|
+
t.match([...pairwise('ABCDE')], [['A', 'B'], ['B', 'C'], ['C', 'D'], ['D', 'E']]);
|
|
236
|
+
t.match([...pairwise([1])], []);
|
|
237
|
+
t.match([...pairwise([])], []);
|
|
238
|
+
t.match([...pairwise(null)], []);
|
|
239
|
+
t.end();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
t.test('itertools/compress', (t) => {
|
|
244
|
+
t.match([...compress('ABCDEF', [1, 0, 1, 0, 1, 1])], ['A', 'C', 'E', 'F']);
|
|
245
|
+
t.match([...compress([1, 2, 3], [true, false])], [1]);
|
|
246
|
+
t.match([...compress(null, [1])], []);
|
|
247
|
+
t.match([...compress([1], null)], []);
|
|
248
|
+
t.end();
|
|
249
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const t = require('tap');
|
|
2
|
+
|
|
3
|
+
const { pick, omit, get } = require('../src/lang/objects.js');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
t.test('objects/pick', (t) => {
|
|
7
|
+
t.match(pick({ a: 1, b: 2, c: 3 }, ['a', 'c']), { a: 1, c: 3 });
|
|
8
|
+
t.match(pick({ a: 1, b: 2 }, 'a'), { a: 1 });
|
|
9
|
+
t.match(pick({ a: 1 }, ['a', 'missing']), { a: 1 });
|
|
10
|
+
t.match(pick(null, ['a']), {});
|
|
11
|
+
t.match(pick(undefined, ['a']), {});
|
|
12
|
+
t.match(pick(1, ['a']), {});
|
|
13
|
+
t.match(pick({ a: 1 }, []), {});
|
|
14
|
+
|
|
15
|
+
const proto = { inherited: 1 };
|
|
16
|
+
const obj = Object.create(proto);
|
|
17
|
+
obj.own = 2;
|
|
18
|
+
t.equal(pick(obj, ['inherited']).inherited, 1);
|
|
19
|
+
t.equal(pick(obj, ['own']).own, 2);
|
|
20
|
+
|
|
21
|
+
const src = {};
|
|
22
|
+
Object.defineProperty(src, '__proto__', {
|
|
23
|
+
value: { polluted: true },
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true
|
|
26
|
+
});
|
|
27
|
+
const picked = pick(src, ['__proto__']);
|
|
28
|
+
t.equal(Object.getPrototypeOf(picked), Object.prototype);
|
|
29
|
+
t.equal(picked.polluted, undefined);
|
|
30
|
+
t.equal(Object.prototype.polluted, undefined);
|
|
31
|
+
|
|
32
|
+
const withCtor = pick({ constructor: 1, prototype: 2, a: 3 }, ['constructor', 'prototype', 'a']);
|
|
33
|
+
t.equal(withCtor.constructor, 1);
|
|
34
|
+
t.equal(withCtor.prototype, 2);
|
|
35
|
+
t.equal(withCtor.a, 3);
|
|
36
|
+
t.end();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
t.test('objects/omit', (t) => {
|
|
41
|
+
t.match(omit({ a: 1, b: 2, c: 3 }, ['b']), { a: 1, c: 3 });
|
|
42
|
+
t.match(omit({ a: 1, b: 2 }, 'a'), { b: 2 });
|
|
43
|
+
t.match(omit({ a: 1 }, ['missing']), { a: 1 });
|
|
44
|
+
t.match(omit(1, ['a']), {});
|
|
45
|
+
t.match(omit(true, ['a']), {});
|
|
46
|
+
t.match(omit(undefined, ['a']), {});
|
|
47
|
+
t.match(omit({ a: 1, b: 2 }, []), { a: 1, b: 2 });
|
|
48
|
+
t.match(omit({ 1: 'one', a: 2 }, [1]), { a: 2 });
|
|
49
|
+
t.end();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
t.test('objects/get', (t) => {
|
|
54
|
+
const data = { a: { b: { c: 3 } }, n: null, u: undefined };
|
|
55
|
+
|
|
56
|
+
t.equal(get(data, 'a.b.c'), 3);
|
|
57
|
+
t.equal(get(data, ['a', 'b', 'c']), 3);
|
|
58
|
+
t.equal(get(data, 'a.b.missing', 'fallback'), 'fallback');
|
|
59
|
+
t.equal(get(data, 'a.missing.c', 'fallback'), 'fallback');
|
|
60
|
+
t.equal(get(data, 'n'), null);
|
|
61
|
+
t.equal(get({ a: 'hello' }, 'a.x', 'fallback'), 'fallback');
|
|
62
|
+
t.equal(get({ a: 1 }, 'a.b', 'fallback'), 'fallback');
|
|
63
|
+
t.equal(get(data, 'u', 'fallback'), 'fallback');
|
|
64
|
+
t.equal(get(null, 'a', 'fallback'), 'fallback');
|
|
65
|
+
t.equal(get(undefined, 'a', 'fallback'), 'fallback');
|
|
66
|
+
t.equal(get(data, ''), data);
|
|
67
|
+
t.equal(get(data, []), data);
|
|
68
|
+
t.equal(get(['x', 'y'], 1), 'y');
|
|
69
|
+
t.equal(get(data, 'a.b'), data.a.b);
|
|
70
|
+
t.end();
|
|
71
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const t = require('tap');
|
|
2
2
|
|
|
3
|
-
const { toInteger } = require('../src/math/numbers.js');
|
|
3
|
+
const { toInteger, toNumber, clamp, mod } = require('../src/math/numbers.js');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
t.test('numbers/toInteger', (t) => {
|
|
@@ -14,3 +14,42 @@ t.test('numbers/toInteger', (t) => {
|
|
|
14
14
|
t.equal(toInteger(Symbol('x')), 0);
|
|
15
15
|
t.end();
|
|
16
16
|
});
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
t.test('numbers/toNumber', (t) => {
|
|
20
|
+
t.equal(toNumber(null), 0);
|
|
21
|
+
t.equal(toNumber(undefined), 0);
|
|
22
|
+
t.equal(toNumber(1.5), 1.5);
|
|
23
|
+
t.equal(toNumber('1.5'), 1.5);
|
|
24
|
+
t.equal(toNumber('1.5abc'), 1.5);
|
|
25
|
+
t.equal(toNumber(1), 1);
|
|
26
|
+
t.equal(toNumber('abcde'), 0);
|
|
27
|
+
t.equal(toNumber(Symbol('x')), 0);
|
|
28
|
+
t.end();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
t.test('numbers/clamp', (t) => {
|
|
33
|
+
t.equal(clamp(5, 0, 10), 5);
|
|
34
|
+
t.equal(clamp(-1, 0, 10), 0);
|
|
35
|
+
t.equal(clamp(11, 0, 10), 10);
|
|
36
|
+
t.equal(clamp(5, 10, 0), 5);
|
|
37
|
+
t.equal(clamp(-3, 10, 0), 0);
|
|
38
|
+
t.equal(clamp(0, 0, 0), 0);
|
|
39
|
+
t.ok(Number.isNaN(clamp(undefined, 0, 10)));
|
|
40
|
+
t.end();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
t.test('numbers/mod', (t) => {
|
|
45
|
+
t.equal(mod(5, 3), 2);
|
|
46
|
+
t.equal(mod(-1, 5), 4);
|
|
47
|
+
t.equal(mod(-5, 3), 1);
|
|
48
|
+
t.equal(mod(5, -3), -1);
|
|
49
|
+
t.equal(mod(0, 3), 0);
|
|
50
|
+
t.equal(mod(5, 0), 0);
|
|
51
|
+
t.equal(mod(null, 3), 0);
|
|
52
|
+
t.equal(mod(5, null), 0);
|
|
53
|
+
t.equal(mod(NaN, 3), 0);
|
|
54
|
+
t.end();
|
|
55
|
+
});
|
package/docs/lang_arrays.md
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
* **compact(arg)**: removes *null* and *undefined* values from an array, returns always an array. If the *arg* is invalid, it returns an empty array.
|
|
4
4
|
* **accumulate(operator, a, b)**: applies the operator to *a* and *b* then returns the result.
|
|
5
|
+
* **unique(arr)**: returns a new array with duplicate values removed, preserving first-seen order. Uses `SameValueZero` equality (so `NaN` matches `NaN`). If *arr* is `null`/`undefined`, returns an empty array.
|
|
6
|
+
* **chunk(arr, size)**: splits *arr* into arrays of length *size*. The last chunk may be shorter. If *arr* is `null`/`undefined` or *size* is less than `1`, returns an empty array.
|
|
7
|
+
* **flatten(arr)**: flattens *arr* by one level. Nested non-arrays are left as-is. If *arr* is `null`/`undefined`, returns an empty array.
|
|
5
8
|
|
|
6
9
|
## Classes
|
|
7
10
|
|
|
8
|
-
* **Operator**: Static class that configures *accumulate* method. Support *addition (Operator.plus)*, *subtraction (Operator.minus)*, *multiplication (Operator.multiply)* and *division (Operator.division)*.
|
|
11
|
+
* **Operator**: Static class that configures *accumulate* method. Support *addition (Operator.plus)*, *subtraction (Operator.minus)*, *multiplication (Operator.multiply)* and *division (Operator.division)*.
|
package/docs/lang_checks.md
CHANGED
|
@@ -5,3 +5,7 @@
|
|
|
5
5
|
* **isObject(arg)**: Checks if the `arg` is an `object` and return `true` else `false`
|
|
6
6
|
* **isString(arg)**: Checks if the `arg` is a `string` and return `true` else `false`
|
|
7
7
|
* **isNumber(arg)**: Checks if the `arg` is a `number` and return `true` else `false`
|
|
8
|
+
* **isBoolean(arg)**: Checks if the `arg` is a `boolean` and return `true` else `false`
|
|
9
|
+
* **isFunction(arg)**: Checks if the `arg` is a `function` (including async functions, generators, and classes) and return `true` else `false`
|
|
10
|
+
* **isInteger(arg)**: Checks if the `arg` is an integer number (`Number.isInteger`) and return `true` else `false`. `NaN`, `Infinity` and floats return `false`.
|
|
11
|
+
* **isEmpty(arg)**: Returns `true` for `null`/`undefined`, empty strings, empty arrays, empty `Map`/`Set`, and objects with no own enumerable keys. Numbers, booleans and functions are never empty.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Lang / collections
|
|
2
|
+
|
|
3
|
+
Python `collections` types that JS is missing.
|
|
4
|
+
|
|
5
|
+
## Counter
|
|
6
|
+
|
|
7
|
+
Count hashable items. Keys use `Map` equality (`SameValueZero`), so objects and `NaN` work.
|
|
8
|
+
|
|
9
|
+
* **new Counter(source)**: *source* may be an iterable (each item counted once per occurrence — `new Counter('aab')` has `a: 2`), a plain object of counts (`{ a: 2 }`), a `Map`, another `Counter`, or omitted for an empty counter.
|
|
10
|
+
* **get(item)**: count of *item*, or `0` if unseen (does not insert — Python `Counter.__missing__`).
|
|
11
|
+
* **add(item, n=1)**: add *n* to the count; returns the new count.
|
|
12
|
+
* **set(item, n)**: set the count; returns the counter.
|
|
13
|
+
* **delete(item)**: drop *item*.
|
|
14
|
+
* **mostCommon(n)**: `[item, count]` pairs, highest count first. Omit *n* for every item. Ties keep insertion order.
|
|
15
|
+
* **total()**: sum of counts (Python 3.10 `Counter.total`).
|
|
16
|
+
* **elements()**: generator that yields each item as many times as its positive count (Python `Counter.elements`).
|
|
17
|
+
* **keys() / values() / entries() / size**: `Map`-like views. The counter itself is iterable over keys (`for (const k of counter)`).
|
|
18
|
+
|
|
19
|
+
## DefaultDict
|
|
20
|
+
|
|
21
|
+
* **new DefaultDict(factory)**: *factory* is called to fill missing keys (Python `collections.defaultdict`). `new DefaultDict(Array)` / `new DefaultDict(() => [])` are the usual list-of-groups pattern.
|
|
22
|
+
* **get(key)**: like Python `d[key]` — inserts `factory()` if missing, then returns the value. If *factory* is not a function, missing keys return `undefined` and are not inserted.
|
|
23
|
+
* **peek(key, defaultValue)**: like Python `dict.get` — does **not** insert.
|
|
24
|
+
* **set(key, value)** / **has(key)** / **delete(key)** / **size** / **keys()** / **values()** / **entries()**.
|
package/docs/lang_functools.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
## lang / functools
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Python `functools`, plus a couple of everyday function helpers.
|
|
4
|
+
|
|
5
|
+
* **reflexive(arg)**: Just a dumb method that returns `arg` as provided. (Python identity: `lambda x: x`.)
|
|
6
|
+
* **partial(fn, ...args)**: Returns a function that invokes *fn* with *args* prepended to whatever arguments it later receives. Preserves `this`. (Python `functools.partial`.)
|
|
7
|
+
* **compose(...fns)**: Right-to-left function composition. `compose(f, g, h)(x)` is `f(g(h(x)))`. With no functions, returns `reflexive`.
|
|
8
|
+
* **once(fn)**: Returns a function that invokes *fn* at most once. Later calls return the first result (including `undefined` if the first call threw after being marked as called).
|
|
9
|
+
* **memoize(fn, resolver)** / **cache(fn, resolver)**: Returns a function that caches *fn* results (`cache` is the Python `functools.cache` name). With no *resolver*, arguments are compared with `SameValueZero` in a nested `Map` (so `memoize(fn)(1, 2)` and `memoize(fn)(1, 2, undefined)` are distinct, and `NaN` is a valid key). With a *resolver*, the cache key is `resolver(...args)`. Failed calls are not cached. The cache is exposed as `.cache` (`Map`); call `.cache.clear()` to drop entries.
|
|
10
|
+
* **reduce(fn, iterable, initializer)**: Fold *iterable* left-to-right with *fn* (Python `functools.reduce`). If *initializer* is omitted, the first item is the start value. An empty iterable with no initializer throws `TypeError`.
|
package/docs/lang_itertools.md
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
# Lang / itertools
|
|
2
2
|
|
|
3
|
+
Python `itertools` (plus a few builtins that belong with it: `zip`, `range`, `enumerate`) as lazy generators.
|
|
4
|
+
|
|
3
5
|
* **count(start, step=1)**: A generator that counts from *start* with a specified *step*. Default stepping is *1*.
|
|
4
|
-
* **repeat(arg, times=0)**: A generator that repeats *arg* for *times* times. If times is 0 then it returns *arg* forever.
|
|
6
|
+
* **repeat(arg, times=0)**: A generator that repeats *arg* for *times* times. If times is 0 then it returns *arg* forever.
|
|
7
|
+
* **cycle(iterable)**: A generator that cycles through *iterable* forever. Values are buffered on the first pass so the source is consumed only once. Empty or `null`/`undefined` iterables yield nothing.
|
|
8
|
+
* **chain(...iterables)**: A generator that yields the items of each iterable in order. `null`/`undefined` arguments are skipped.
|
|
9
|
+
* **take(n, iterable)**: Consumes the first *n* items from *iterable* and returns them as an array. Stops early if the iterable is exhausted. If *n* is less than `1` or *iterable* is `null`/`undefined`, returns an empty array.
|
|
10
|
+
* **zip(...iterables)**: A generator that yields arrays of aligned items, stopping when the shortest iterable is exhausted (Python `zip`). Returns an empty generator if called with no arguments or if any argument is not iterable.
|
|
11
|
+
* **range(start, stop, step=1)**: A generator of numbers with an exclusive end, matching Python `range`. `range(stop)` counts from `0`. `range(start, stop)` and `range(start, stop, step)` are also supported, including negative steps. A non-finite bound or a `0` step yields nothing. The index form `start + i * step` is used so float steps do not accumulate rounding error.
|
|
12
|
+
* **enumerate(iterable, start=0)**: A generator that yields `[index, value]` pairs, starting at *start* (Python `enumerate`). `null`/`undefined` iterables yield nothing.
|
|
13
|
+
* **islice(iterable, stop)** / **islice(iterable, start, stop, step=1)**: Slice an iterator (Python `itertools.islice`). Negative indices are not supported. `stop` of `null` means "until exhausted". A `step` less than `1` yields nothing.
|
|
14
|
+
* **takewhile(predicate, iterable)**: Yield items as long as *predicate* is truthy, then stop (Python `itertools.takewhile`).
|
|
15
|
+
* **dropwhile(predicate, iterable)**: Skip items as long as *predicate* is truthy, then yield the rest (Python `itertools.dropwhile`).
|
|
16
|
+
* **filterfalse(predicate, iterable)**: Yield items for which *predicate* is falsy (Python `itertools.filterfalse`).
|
|
17
|
+
* **zip_longest(...iterables, { fillvalue })**: Like `zip`, but continues until the longest iterable is exhausted. Missing values are *fillvalue* (default `undefined`). Pass `{ fillvalue }` as the last argument (Python `itertools.zip_longest`).
|
|
18
|
+
* **product(...iterables)**: Cartesian product. `product()` with no arguments yields one empty array, matching Python. An empty input iterable yields nothing.
|
|
19
|
+
* **groupby(iterable, key)**: Consecutive grouping (Python `itertools.groupby`): a new group starts only when the key *changes*, so the input is not sorted. Each yield is `[key, items]` where *items* is an array (consumed immediately, unlike Python's shared group iterator). Without *key*, the item itself is the key. Equality is `SameValueZero`.
|
|
20
|
+
* **starmap(fn, iterable)**: `fn(...args)` for each argument array in *iterable* (Python `itertools.starmap`).
|
|
21
|
+
* **batched(iterable, n)**: Yield arrays of length *n*; the last batch may be shorter (Python 3.12 `itertools.batched`). *n* less than `1` yields nothing.
|
|
22
|
+
* **pairwise(iterable)**: Overlapping pairs `(s0, s1), (s1, s2), ...` (Python 3.10 `itertools.pairwise`).
|
|
23
|
+
* **compress(data, selectors)**: Yield items from *data* whose matching *selector* is truthy (Python `itertools.compress`). Stops when either input is exhausted.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Lang / Objects
|
|
2
|
+
|
|
3
|
+
* **pick(obj, keys)**: returns a new object with only the listed *keys* (an array, or a single key). Missing keys are skipped. If *obj* is `null`/`undefined` or not an object, returns `{}`. Assignments of `__proto__` / `prototype` / `constructor` use `defineProperty` so the result is not prototype-polluted.
|
|
4
|
+
* **omit(obj, keys)**: returns a new object with the listed *keys* removed. If *obj* is `null`/`undefined` or not an object, returns `{}`.
|
|
5
|
+
* **get(obj, path, defaultValue)**: reads a nested value. *path* may be a dotted string (`'a.b.c'`), an array of keys (`['a', 'b', 'c']`), or a single key. If any step is missing, or the resolved value is `undefined`, returns *defaultValue*. An existing `null` is returned as `null`, not as the default. If *path* is an empty string or empty array, returns *obj* itself.
|
package/docs/math_numbers.md
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
# Math / Numbers
|
|
2
2
|
|
|
3
|
-
* **toInteger(arg)**: Attempts to convert `arg` to `Integer`. Return `0` if the arg is `zero` or invalid value.
|
|
3
|
+
* **toInteger(arg)**: Attempts to convert `arg` to `Integer`. Return `0` if the arg is `zero` or invalid value.
|
|
4
|
+
* **toNumber(arg)**: Attempts to convert `arg` to a number via `parseFloat`. Return `0` if the arg is `null`/`undefined` or not numeric. Unlike `toInteger`, fractional values are kept (`toNumber('1.5')` is `1.5`).
|
|
5
|
+
* **clamp(value, min, max)**: Constrains *value* to the inclusive range `[*min*, *max*]`. If *min* is greater than *max*, the bounds are swapped. Non-numeric *value* yields `NaN` (the same as `Number(value)`).
|
|
6
|
+
* **mod(n, m)**: Mathematical modulo whose sign follows the divisor (Python `%` / floored division). `mod(-1, 5)` is `4`; JavaScript's `%` would return `-1`. Return `0` if either argument is not finite or if *m* is `0`.
|
package/index.js
CHANGED
|
@@ -6,6 +6,8 @@ const arrays = require('./src/lang/arrays.js');
|
|
|
6
6
|
const files = require('./src/io/files.js');
|
|
7
7
|
const flow = require('./src/lang/flow.js');
|
|
8
8
|
const itertools = require('./src/lang/itertools.js');
|
|
9
|
+
const objects = require('./src/lang/objects.js');
|
|
10
|
+
const collections = require('./src/lang/collections.js');
|
|
9
11
|
|
|
10
12
|
const { BadlyInitializedError } = require('./src/internal/exceptions.js');
|
|
11
13
|
|
|
@@ -20,7 +22,9 @@ module.exports = {
|
|
|
20
22
|
flow: flow,
|
|
21
23
|
checks: checks,
|
|
22
24
|
functools: functools,
|
|
23
|
-
itertools: itertools
|
|
25
|
+
itertools: itertools,
|
|
26
|
+
objects: objects,
|
|
27
|
+
collections: collections
|
|
24
28
|
},
|
|
25
29
|
math: {
|
|
26
30
|
numbers: numbers
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
},
|
|
6
6
|
"description": "Simple but useful hacks for everyday javascript programming",
|
|
7
7
|
"devDependencies": {
|
|
8
|
-
"tap": "21.7.
|
|
8
|
+
"tap": "21.7.5"
|
|
9
9
|
},
|
|
10
10
|
"homepage": "https://github.com/wizhut/wizjs#readme",
|
|
11
11
|
"keywords": [
|
|
@@ -24,5 +24,5 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"test": "tap run"
|
|
26
26
|
},
|
|
27
|
-
"version": "0.1.
|
|
27
|
+
"version": "0.1.7"
|
|
28
28
|
}
|
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Wizjs
|
|
2
2
|
|
|
3
|
-
A Javascript library
|
|
3
|
+
A Javascript library that ports selected Python idioms and stdlib helpers (`itertools`, `functools`, `collections`) to everyday JS. A few selected dependencies only. No, this library will not become another `lodash` :).
|
|
4
4
|
|
|
5
5
|
Use by importing:
|
|
6
6
|
|
|
@@ -17,7 +17,9 @@ Use by importing:
|
|
|
17
17
|
flow: [functions],
|
|
18
18
|
singleton: [functions],
|
|
19
19
|
functools: [functions],
|
|
20
|
-
itertools: [functions]
|
|
20
|
+
itertools: [functions],
|
|
21
|
+
objects: [functions],
|
|
22
|
+
collections: [functions]
|
|
21
23
|
},
|
|
22
24
|
math: {
|
|
23
25
|
numbers: [functions]
|
|
@@ -40,6 +42,8 @@ You can also import individual functions like the following snippet:
|
|
|
40
42
|
* Control-**Flow** utilities ... [[docs](docs/lang_flow.md)]
|
|
41
43
|
* **functools** ... [[docs](docs/lang_functools.md)]
|
|
42
44
|
* **itertools** ... [[docs](docs/lang_itertools.md)]
|
|
45
|
+
* **Objects** utility functions ... [[docs](docs/lang_objects.md)]
|
|
46
|
+
* **collections** (`Counter`, `DefaultDict`) ... [[docs](docs/lang_collections.md)]
|
|
43
47
|
* **Singleton** hack ... [[docs](docs/lang_singleton.md)]
|
|
44
48
|
|
|
45
49
|
### Math
|
package/src/lang/arrays.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const { isNil } = require('./checks.js');
|
|
1
|
+
const { isNil, isArray } = require('./checks.js');
|
|
2
|
+
const { toInteger } = require('../math/numbers.js');
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
function compact(arr) {
|
|
@@ -42,8 +43,78 @@ function accumulate(arr, operator, initial=0) {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
|
|
46
|
+
function unique(arr) {
|
|
47
|
+
if (isNil(arr)) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
const result = [];
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < arr.length; i++) {
|
|
55
|
+
const value = arr[i];
|
|
56
|
+
|
|
57
|
+
if (seen.has(value)) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
seen.add(value);
|
|
62
|
+
result.push(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
function chunk(arr, size) {
|
|
70
|
+
if (isNil(arr)) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const n = toInteger(size);
|
|
75
|
+
|
|
76
|
+
if (n < 1) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const result = [];
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < arr.length; i += n) {
|
|
83
|
+
result.push(Array.prototype.slice.call(arr, i, i + n));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
function flatten(arr) {
|
|
91
|
+
if (isNil(arr)) {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const result = [];
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < arr.length; i++) {
|
|
98
|
+
const item = arr[i];
|
|
99
|
+
|
|
100
|
+
if (isArray(item)) {
|
|
101
|
+
for (let j = 0; j < item.length; j++) {
|
|
102
|
+
result.push(item[j]);
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
result.push(item);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
45
113
|
module.exports = {
|
|
46
114
|
compact,
|
|
47
115
|
accumulate,
|
|
48
|
-
Operator
|
|
49
|
-
|
|
116
|
+
Operator,
|
|
117
|
+
unique,
|
|
118
|
+
chunk,
|
|
119
|
+
flatten
|
|
120
|
+
}
|
package/src/lang/checks.js
CHANGED
|
@@ -30,10 +30,50 @@ function isNumber(value) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
function isBoolean(value) {
|
|
34
|
+
return typeof value === 'boolean';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
function isFunction(value) {
|
|
39
|
+
return typeof value === 'function';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
function isInteger(value) {
|
|
44
|
+
return Number.isInteger(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
function isEmpty(value) {
|
|
49
|
+
if (isNil(value)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (isString(value) || isArray(value)) {
|
|
54
|
+
return value.length === 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (value instanceof Map || value instanceof Set) {
|
|
58
|
+
return value.size === 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isObject(value)) {
|
|
62
|
+
return Object.keys(value).length === 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
33
69
|
module.exports = {
|
|
34
70
|
isNil,
|
|
35
71
|
isArray,
|
|
36
72
|
isObject,
|
|
37
73
|
isString,
|
|
38
|
-
isNumber
|
|
39
|
-
|
|
74
|
+
isNumber,
|
|
75
|
+
isBoolean,
|
|
76
|
+
isFunction,
|
|
77
|
+
isInteger,
|
|
78
|
+
isEmpty
|
|
79
|
+
}
|