@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.
@@ -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
+ }