Clean up iterable utilities
This commit is contained in:
parent
bd48dea613
commit
1a9c6b9385
6 changed files with 105 additions and 68 deletions
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import { isPlainObject } from 'lodash';
|
import { isPlainObject } from 'lodash';
|
||||||
|
|
||||||
import { isIterable } from '../util/isIterable';
|
import { isIterable } from '../util/iterables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IPC arguments are serialized with the [structured clone algorithm][0], but we can only
|
* IPC arguments are serialized with the [structured clone algorithm][0], but we can only
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import { assert } from 'chai';
|
|
||||||
|
|
||||||
import { isIterable } from '../../util/isIterable';
|
|
||||||
|
|
||||||
describe('isIterable', () => {
|
|
||||||
it('returns false for non-iterables', () => {
|
|
||||||
assert.isFalse(isIterable(undefined));
|
|
||||||
assert.isFalse(isIterable(null));
|
|
||||||
assert.isFalse(isIterable(123));
|
|
||||||
assert.isFalse(isIterable({ foo: 'bar' }));
|
|
||||||
assert.isFalse(
|
|
||||||
isIterable({
|
|
||||||
length: 2,
|
|
||||||
'0': 'fake',
|
|
||||||
'1': 'array',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns true for iterables', () => {
|
|
||||||
assert.isTrue(isIterable('strings are iterable'));
|
|
||||||
assert.isTrue(isIterable(['arrays too']));
|
|
||||||
assert.isTrue(isIterable(new Set('and sets')));
|
|
||||||
assert.isTrue(isIterable(new Map([['and', 'maps']])));
|
|
||||||
assert.isTrue(
|
|
||||||
isIterable({
|
|
||||||
[Symbol.iterator]() {
|
|
||||||
return {
|
|
||||||
next() {
|
|
||||||
return {
|
|
||||||
value: 'endless iterable',
|
|
||||||
done: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
assert.isTrue(
|
|
||||||
isIterable(
|
|
||||||
(function* generators() {
|
|
||||||
yield 123;
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -4,9 +4,84 @@
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
|
|
||||||
import { map, take } from '../../util/iterables';
|
import { isIterable, size, map, take } from '../../util/iterables';
|
||||||
|
|
||||||
describe('iterable utilities', () => {
|
describe('iterable utilities', () => {
|
||||||
|
describe('isIterable', () => {
|
||||||
|
it('returns false for non-iterables', () => {
|
||||||
|
assert.isFalse(isIterable(undefined));
|
||||||
|
assert.isFalse(isIterable(null));
|
||||||
|
assert.isFalse(isIterable(123));
|
||||||
|
assert.isFalse(isIterable({ foo: 'bar' }));
|
||||||
|
assert.isFalse(
|
||||||
|
isIterable({
|
||||||
|
length: 2,
|
||||||
|
'0': 'fake',
|
||||||
|
'1': 'array',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true for iterables', () => {
|
||||||
|
assert.isTrue(isIterable('strings are iterable'));
|
||||||
|
assert.isTrue(isIterable(['arrays too']));
|
||||||
|
assert.isTrue(isIterable(new Set('and sets')));
|
||||||
|
assert.isTrue(isIterable(new Map([['and', 'maps']])));
|
||||||
|
assert.isTrue(
|
||||||
|
isIterable({
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
return {
|
||||||
|
next() {
|
||||||
|
return {
|
||||||
|
value: 'endless iterable',
|
||||||
|
done: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert.isTrue(
|
||||||
|
isIterable(
|
||||||
|
(function* generators() {
|
||||||
|
yield 123;
|
||||||
|
})()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('size', () => {
|
||||||
|
it('returns the length of a string', () => {
|
||||||
|
assert.strictEqual(size(''), 0);
|
||||||
|
assert.strictEqual(size('hello world'), 11);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the length of an array', () => {
|
||||||
|
assert.strictEqual(size([]), 0);
|
||||||
|
assert.strictEqual(size(['hello', 'world']), 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the size of a set', () => {
|
||||||
|
assert.strictEqual(size(new Set()), 0);
|
||||||
|
assert.strictEqual(size(new Set([1, 2, 3])), 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the length (not byte length) of typed arrays', () => {
|
||||||
|
assert.strictEqual(size(new Uint8Array(3)), 3);
|
||||||
|
assert.strictEqual(size(new Uint32Array(3)), 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the size of arbitrary iterables', () => {
|
||||||
|
function* someNumbers() {
|
||||||
|
yield 3;
|
||||||
|
yield 6;
|
||||||
|
yield 9;
|
||||||
|
}
|
||||||
|
assert.strictEqual(size(someNumbers()), 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('map', () => {
|
describe('map', () => {
|
||||||
it('returns an empty iterable when passed an empty iterable', () => {
|
it('returns an empty iterable when passed an empty iterable', () => {
|
||||||
const fn = sinon.fake();
|
const fn = sinon.fake();
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { size } from './iterables';
|
||||||
|
|
||||||
export function count(str: string): number {
|
export function count(str: string): number {
|
||||||
const segments = new Intl.Segmenter().segment(str);
|
const segments = new Intl.Segmenter().segment(str);
|
||||||
const iterator = segments[Symbol.iterator]();
|
return size(segments);
|
||||||
|
|
||||||
let result = -1;
|
|
||||||
for (let done = false; !done; result += 1) {
|
|
||||||
done = Boolean(iterator.next().done);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
export function isIterable(value: unknown): value is Iterable<unknown> {
|
|
||||||
return (
|
|
||||||
(typeof value === 'object' && value !== null && Symbol.iterator in value) ||
|
|
||||||
typeof value === 'string'
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -3,6 +3,31 @@
|
||||||
|
|
||||||
/* eslint-disable max-classes-per-file */
|
/* eslint-disable max-classes-per-file */
|
||||||
|
|
||||||
|
export function isIterable(value: unknown): value is Iterable<unknown> {
|
||||||
|
return (
|
||||||
|
(typeof value === 'object' && value !== null && Symbol.iterator in value) ||
|
||||||
|
typeof value === 'string'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function size(iterable: Iterable<unknown>): number {
|
||||||
|
// We check for common types as an optimization.
|
||||||
|
if (typeof iterable === 'string' || Array.isArray(iterable)) {
|
||||||
|
return iterable.length;
|
||||||
|
}
|
||||||
|
if (iterable instanceof Set || iterable instanceof Map) {
|
||||||
|
return iterable.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
const iterator = iterable[Symbol.iterator]();
|
||||||
|
|
||||||
|
let result = -1;
|
||||||
|
for (let done = false; !done; result += 1) {
|
||||||
|
done = Boolean(iterator.next().done);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export function map<T, ResultT>(
|
export function map<T, ResultT>(
|
||||||
iterable: Iterable<T>,
|
iterable: Iterable<T>,
|
||||||
fn: (value: T) => ResultT
|
fn: (value: T) => ResultT
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue