@bodil/monkey-business 0.3.3 → 0.3.5
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/dist/array.d.ts +59 -0
- package/dist/array.js +100 -0
- package/dist/array.js.map +1 -0
- package/dist/index.d.ts +36 -136
- package/dist/index.js +37 -207
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +32 -3
- package/dist/index.test.js.map +1 -1
- package/dist/iterator.d.ts +78 -0
- package/dist/iterator.js +176 -0
- package/dist/iterator.js.map +1 -0
- package/dist/map.d.ts +19 -0
- package/dist/map.js +31 -0
- package/dist/map.js.map +1 -0
- package/dist/object.d.ts +12 -0
- package/dist/object.js +17 -0
- package/dist/object.js.map +1 -0
- package/dist/weakmap.d.ts +19 -0
- package/dist/weakmap.js +31 -0
- package/dist/weakmap.js.map +1 -0
- package/package.json +71 -18
- package/src/array.ts +128 -0
- package/src/es-iterator-helpers.d.ts +24 -0
- package/src/index.test.ts +39 -7
- package/src/index.ts +78 -407
- package/src/iterator.ts +223 -0
- package/src/map.ts +37 -0
- package/src/object.ts +21 -0
- package/src/weakmap.ts +37 -0
package/src/iterator.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-this-alias */
|
|
2
|
+
// deno-lint-ignore-file no-this-alias
|
|
3
|
+
|
|
4
|
+
import { assert } from "@bodil/core/assert";
|
|
5
|
+
import type { Present } from "@bodil/core/types";
|
|
6
|
+
import { None, type Option, Some } from "@bodil/opt";
|
|
7
|
+
import getConcatPolyfill from "es-iterator-helpers/Iterator.concat/polyfill";
|
|
8
|
+
import getChunksPolyfill from "es-iterator-helpers/Iterator.prototype.chunks/polyfill";
|
|
9
|
+
import getZipPolyfill from "es-iterator-helpers/Iterator.zip/polyfill";
|
|
10
|
+
import getZipKeyedPolyfill from "es-iterator-helpers/Iterator.zipKeyed/polyfill";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Sequence multiple iterators into a single one.
|
|
14
|
+
* @see https://github.com/tc39/proposal-iterator-sequencing
|
|
15
|
+
* @function
|
|
16
|
+
*/
|
|
17
|
+
export const concat: <T>(...inputs: Array<Iterable<T>>) => IteratorObject<T> = getConcatPolyfill();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @see https://github.com/tc39/proposal-joint-iteration
|
|
21
|
+
* @function
|
|
22
|
+
*/
|
|
23
|
+
export const zip: <T>(inputs: Iterable<Iterable<T>>) => IteratorObject<Array<T>> = getZipPolyfill();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @see https://github.com/tc39/proposal-joint-iteration
|
|
27
|
+
* @function
|
|
28
|
+
*/
|
|
29
|
+
export const zipKeyed: <T>(
|
|
30
|
+
inputs: Record<string, Iterable<T>>,
|
|
31
|
+
) => IteratorObject<Record<string, T>> = getZipKeyedPolyfill();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Split the input up into arrays of at most `size` items.
|
|
35
|
+
* @function
|
|
36
|
+
*/
|
|
37
|
+
export const chunks: <T>(
|
|
38
|
+
this: Iterator<T>,
|
|
39
|
+
size: number,
|
|
40
|
+
) => IteratorObject<Array<T>, undefined> = getChunksPolyfill();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Construct an empty iterator.
|
|
44
|
+
*/
|
|
45
|
+
export function empty<T>(): IteratorObject<T> {
|
|
46
|
+
return Iterator.from([]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the next value from the iterator, wrapped in an {@link Option}
|
|
51
|
+
* type. If the iterator has no more values, return {@link None}.
|
|
52
|
+
*/
|
|
53
|
+
export function takeOne<T>(this: Iterator<T>): Option<T> {
|
|
54
|
+
const result = this.next();
|
|
55
|
+
return result.done === true ? None : Some(result.value);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Take items from the iterator only until the first time `predicate` returns false.
|
|
60
|
+
*/
|
|
61
|
+
export function takeWhile<T>(
|
|
62
|
+
this: IteratorObject<T, undefined>,
|
|
63
|
+
predicate: (value: T, index: number) => boolean,
|
|
64
|
+
): IteratorObject<T, undefined> {
|
|
65
|
+
const iter = this;
|
|
66
|
+
let index = 0;
|
|
67
|
+
let done = false;
|
|
68
|
+
return Iterator.from<T>({
|
|
69
|
+
next(): IteratorResult<T, undefined> {
|
|
70
|
+
if (!done) {
|
|
71
|
+
const result = iter.next();
|
|
72
|
+
if (result.done === true) {
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
if (predicate(result.value, index++)) {
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
done = true;
|
|
80
|
+
return { done: true, value: undefined };
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Ignore all items matching `predicate` until the first item that
|
|
87
|
+
* doesn't match.
|
|
88
|
+
*/
|
|
89
|
+
export function skipWhile<T>(
|
|
90
|
+
this: IteratorObject<T, undefined>,
|
|
91
|
+
predicate: (value: T, index: number) => boolean,
|
|
92
|
+
): IteratorObject<T, undefined> {
|
|
93
|
+
const iter = this;
|
|
94
|
+
let index = 0;
|
|
95
|
+
let skipped = false;
|
|
96
|
+
return Iterator.from<T>({
|
|
97
|
+
next(): IteratorResult<T, undefined> {
|
|
98
|
+
if (skipped) {
|
|
99
|
+
return iter.next();
|
|
100
|
+
}
|
|
101
|
+
while (true) {
|
|
102
|
+
const result = iter.next();
|
|
103
|
+
if (result.done === true) {
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
if (!predicate(result.value, index++)) {
|
|
107
|
+
skipped = true;
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Iterate until calling `mapFn` on an item returns a value, then return
|
|
117
|
+
* that value. Return `undefined` if `mapFn` never returns a value.
|
|
118
|
+
*/
|
|
119
|
+
export function findMap<T, U>(
|
|
120
|
+
this: IteratorObject<T, undefined>,
|
|
121
|
+
mapFn: (value: T, index: number) => NonNullable<U> | undefined,
|
|
122
|
+
): NonNullable<U> | undefined {
|
|
123
|
+
let index = 0;
|
|
124
|
+
while (true) {
|
|
125
|
+
const result = this.next();
|
|
126
|
+
if (result.done === true) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const mapped = mapFn(result.value, index++);
|
|
130
|
+
if (mapped !== undefined) {
|
|
131
|
+
return mapped;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Split the input up into arrays of at most `size` items.
|
|
138
|
+
*
|
|
139
|
+
* @deprecated This has been standardised as {@link IteratorObject#chunks}.
|
|
140
|
+
*/
|
|
141
|
+
export function partition<T>(
|
|
142
|
+
this: IteratorObject<T, undefined>,
|
|
143
|
+
size: number,
|
|
144
|
+
): IteratorObject<Array<T>, undefined> {
|
|
145
|
+
return (chunks<T>).call(this, size);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* As {@link IteratorObject#partition}, but if the input length isn't
|
|
150
|
+
* divisible by `size`, ensure the *first* array is the shortest one
|
|
151
|
+
* rather than the last.
|
|
152
|
+
*
|
|
153
|
+
* Be warned that this requires the entire input iterable to be read up
|
|
154
|
+
* front to decide its length, so this function is not lazy.
|
|
155
|
+
*/
|
|
156
|
+
export function frontBiasedPartition<T>(
|
|
157
|
+
this: IteratorObject<T, undefined>,
|
|
158
|
+
size: number,
|
|
159
|
+
): Array<Array<T>> {
|
|
160
|
+
const input = this.toArray();
|
|
161
|
+
let inputPos = 0;
|
|
162
|
+
const firstLength = input.length % size;
|
|
163
|
+
const output: Array<Array<T>> = [];
|
|
164
|
+
if (firstLength > 0) {
|
|
165
|
+
output.push(input.slice(0, firstLength));
|
|
166
|
+
inputPos = firstLength;
|
|
167
|
+
}
|
|
168
|
+
while (inputPos < input.length) {
|
|
169
|
+
output.push(input.slice(inputPos, inputPos + size));
|
|
170
|
+
inputPos += size;
|
|
171
|
+
}
|
|
172
|
+
assert(inputPos === input.length);
|
|
173
|
+
return output;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Filter out any items which are `undefined`.
|
|
178
|
+
*/
|
|
179
|
+
export function present<T>(
|
|
180
|
+
this: IteratorObject<T, undefined>,
|
|
181
|
+
): IteratorObject<Present<T>, undefined> {
|
|
182
|
+
return Iterator.from(this.filter((item): item is Present<T> => item !== undefined));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Filter out any items which are `null` or `undefined`.
|
|
187
|
+
*/
|
|
188
|
+
export function nonNullable<T>(
|
|
189
|
+
this: IteratorObject<T, undefined>,
|
|
190
|
+
): IteratorObject<NonNullable<T>, undefined> {
|
|
191
|
+
return Iterator.from(
|
|
192
|
+
this.filter((item): item is NonNullable<T> => item !== undefined && item !== null),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Test whether the given Iterable has the same contents as this
|
|
198
|
+
* iterator.
|
|
199
|
+
*
|
|
200
|
+
* Note that this will consume this iterator up to the point
|
|
201
|
+
* where the values diverge. You should not rely on any particular state
|
|
202
|
+
* of the iterator afterwards, but consider it spent.
|
|
203
|
+
*/
|
|
204
|
+
export function equals(
|
|
205
|
+
this: IteratorObject<unknown, undefined>,
|
|
206
|
+
value: Iterable<unknown>,
|
|
207
|
+
equalsFn: (a: unknown, b: unknown) => boolean = Object.is,
|
|
208
|
+
): boolean {
|
|
209
|
+
const other = Iterator.from(value);
|
|
210
|
+
while (true) {
|
|
211
|
+
const a = this.next();
|
|
212
|
+
const b = other.next();
|
|
213
|
+
if (
|
|
214
|
+
a.done !== b.done ||
|
|
215
|
+
(a.done === false && b.done === false && !equalsFn(a.value, b.value))
|
|
216
|
+
) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (a.done === true && b.done === true) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
package/src/map.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { None, type Option, Some } from "@bodil/opt";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* If `key` exists in the map, return the value associated with it.
|
|
5
|
+
* Otherwise, call `defaultValue()` to create a new value, insert that
|
|
6
|
+
* into the map under `key`, and return it.
|
|
7
|
+
*/
|
|
8
|
+
export function getOrInsertComputed<K, V>(
|
|
9
|
+
this: Map<K, NonNullable<V>>,
|
|
10
|
+
key: K,
|
|
11
|
+
defaultValue: (key: K) => NonNullable<V>,
|
|
12
|
+
): V {
|
|
13
|
+
const result = this.get(key);
|
|
14
|
+
if (result !== undefined) {
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
const newValue = defaultValue(key);
|
|
18
|
+
this.set(key, newValue);
|
|
19
|
+
return newValue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Alias for {@link getOrInsertComputed}.
|
|
24
|
+
* @function
|
|
25
|
+
*/
|
|
26
|
+
export const getOrSet = getOrInsertComputed;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* If `key` exists in the map, remove the key/value pair from the map
|
|
30
|
+
* and return the removed value. Otherwise, return {@link None}.
|
|
31
|
+
*
|
|
32
|
+
* @see {@link Map.delete}
|
|
33
|
+
*/
|
|
34
|
+
export function remove<K, V>(this: Map<K, V>, key: K): Option<V> {
|
|
35
|
+
const result = this.get(key);
|
|
36
|
+
return this.delete(key) ? Some(result!) : None;
|
|
37
|
+
}
|
package/src/object.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { None, type Option, Some } from "@bodil/opt";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checked key lookup. Returns {@link None} if a key isn't present.
|
|
5
|
+
* Returns a {@link Some} if the key is present, even if the value is
|
|
6
|
+
* `undefined`.
|
|
7
|
+
*/
|
|
8
|
+
export function get<O extends object, K extends keyof O>(obj: O, key: K): Option<O[K]> {
|
|
9
|
+
return Object.hasOwn(obj, key) ? Some(obj[key]) : None;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a new object by mapping an existing object's `Object.entries`
|
|
14
|
+
* through a function.
|
|
15
|
+
*/
|
|
16
|
+
export function mapEntries<A, B>(
|
|
17
|
+
obj: Record<string, A>,
|
|
18
|
+
fn: (entry: [key: string, value: A]) => [string, B],
|
|
19
|
+
): Record<string, B> {
|
|
20
|
+
return Object.fromEntries(Object.entries(obj).map(fn));
|
|
21
|
+
}
|
package/src/weakmap.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { None, type Option, Some } from "@bodil/opt";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* If `key` exists in the map, return the value associated with it.
|
|
5
|
+
* Otherwise, call `defaultValue()` to create a new value, insert that
|
|
6
|
+
* into the map under `key`, and return it.
|
|
7
|
+
*/
|
|
8
|
+
export function getOrInsertComputed<K extends WeakKey, V>(
|
|
9
|
+
this: WeakMap<K, NonNullable<V>>,
|
|
10
|
+
key: K,
|
|
11
|
+
defaultValue: (key: K) => NonNullable<V>,
|
|
12
|
+
): V {
|
|
13
|
+
const result = this.get(key);
|
|
14
|
+
if (result !== undefined) {
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
const newValue = defaultValue(key);
|
|
18
|
+
this.set(key, newValue);
|
|
19
|
+
return newValue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Alias for {@link getOrInsertComputed}.
|
|
24
|
+
* @function
|
|
25
|
+
*/
|
|
26
|
+
export const getOrSet = getOrInsertComputed;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* If `key` exists in the map, remove the key/value pair from the map
|
|
30
|
+
* and return the removed value. Otherwise, return {@link None}.
|
|
31
|
+
*
|
|
32
|
+
* @see {@link WeakMap.delete}
|
|
33
|
+
*/
|
|
34
|
+
export function remove<K extends WeakKey, V>(this: WeakMap<K, V>, key: K): Option<V> {
|
|
35
|
+
const result = this.get(key);
|
|
36
|
+
return this.delete(key) ? Some(result!) : None;
|
|
37
|
+
}
|