shelving
Version:
Toolkit for using data in JavaScript.
202 lines (201 loc) • 8.37 kB
JavaScript
import { RequiredError } from "../error/RequiredError.js";
import { interleaveItems, isIterable, omitItems, pickItems } from "./iterate.js";
export function isArray(value, min = 0, max = Number.POSITIVE_INFINITY) {
return Array.isArray(value) && value.length >= min && value.length <= max;
}
export function assertArray(value, min, max, caller = assertArray) {
if (!isArray(value, min, max))
throw new RequiredError(`Must be array${min !== undefined || max !== undefined ? ` with ${min ?? 0} to ${max ?? "∞"} items` : ""}`, {
received: value,
caller,
});
}
/** Convert a possible array to an array. */
export function getArray(list) {
return Array.isArray(list) ? list : isIterable(list) ? Array.from(list) : undefined;
}
export function requireArray(list, min, max, caller = requireArray) {
const arr = Array.isArray(list) ? list : Array.from(list);
assertArray(arr, min, max, caller);
return arr;
}
/** Is an unknown value an item in a specified array or iterable? */
export function isArrayItem(list, item) {
if (isArray(list))
list.includes(item);
for (const i of list)
if (i === item)
return true;
return false;
}
/** Assert that an unknown value is an item in a specified array. */
export function assertArrayItem(arr, item, caller = assertArrayItem) {
if (!isArrayItem(arr, item))
throw new RequiredError("Item must exist in array", { item, array: arr, caller });
}
/** Add multiple items to an array (immutably) and return a new array with those items (or the same array if no changes were made). */
export function withArrayItems(list, ...add) {
const arr = Array.from(list);
const extras = add.filter(_doesNotInclude, arr);
return extras.length ? [...arr, ...extras] : isArray(list) ? list : arr;
}
function _doesNotInclude(value) {
return !this.includes(value);
}
/** Add an item to an array (immutably) and return a new array with that item (or the same array if no changes were made). */
export const withArrayItem = withArrayItems;
/** Pick multiple items from an array (immutably) and return a new array with those items (or the same array if no changes were made). */
export function pickArrayItems(items, ...pick) {
const arr = Array.from(pickItems(items, ...pick));
return isArray(items) && arr.length === items.length ? items : arr;
}
/** Pick an item from an array (immutably) and return a new array with that item (or the same array if no changes were made). */
export const pickArrayItem = pickArrayItems;
/** Remove multiple items from an array (immutably) and return a new array without those items (or the same array if no changes were made). */
export function omitArrayItems(items, ...omit) {
const filtered = Array.from(omitItems(items, ...omit));
return isArray(items) && filtered.length === items.length ? items : filtered;
}
/** Remove an item from an array (immutably) and return a new array without those items (or the same array if no changes were made). */
export const omitArrayItem = omitArrayItems;
/** Toggle an item in and out of an array (immutably) and return a new array with or without the specified items (or the same array if no changes were made). */
export function toggleArrayItems(items, ...toggle) {
const arr = Array.from(items);
const extras = toggle.filter(_doesNotInclude, arr);
const filtered = arr.filter(_doesNotInclude, toggle);
return extras.length ? [...filtered, ...extras] : filtered.length !== arr.length ? filtered : isArray(items) ? items : arr;
}
/** Toggle an item in and out of an array (immutably) and return a new array with or without the specified item (or the same array if no changes were made). */
export const toggleArrayItem = toggleArrayItems;
/** Return a shuffled version of an array or iterable. */
export function shuffleArray(items) {
const arr = Array.from(items);
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
/**
* Add an item to an array (by reference) and return the item.
* - Skip items that already exist.
*/
export function addArrayItem(arr, item) {
if (arr.indexOf(item) < 0)
arr.push(item);
return item;
}
/**
* Add multiple items to an array (by reference).
* - Skip items that already exist.
*/
export function addArrayItems(arr, ...items) {
for (const item of items)
if (arr.indexOf(item) < 0)
arr.push(item);
}
/** Remove multiple items from an array (by reference). */
export function deleteArrayItems(arr, ...items) {
for (let i = arr.length - 1; i >= 0; i--)
if (i in arr && items.includes(arr[i]))
arr.splice(i, 1);
}
/** Remove an item from an array (by reference). */
export const deleteArrayItem = deleteArrayItems;
/** Return an array of the unique items in an array. */
export function getUniqueArray(list) {
const output = [];
for (const item of list)
if (!output.includes(item))
output.push(item);
return isArray(list) && list.length === output.length ? list : output;
}
/** Apply a limit to an array. */
export function limitArray(list, limit) {
const arr = requireArray(list, undefined, undefined, limitArray);
return limit > arr.length ? arr : arr.slice(0, limit);
}
/** Count the items in an array. */
export function countArray(arr) {
return arr.length;
}
export function interleaveArray(items, separator) {
if (isArray(items) && items.length < 2)
return items; // Return same empty array if empty or only one item.
return Array.from(interleaveItems(items, separator));
}
/** Return a new array with a new value replacing a specific index in the array (or the same array if the value was unchanged). */
export function withArrayIndex(arr, index, value) {
if (arr[index] === value)
return arr;
return [...arr.slice(0, index), value, ...arr.slice(index + 1)];
}
/** Return a new array without a specific index in the array (or the same array if the value was unchanged). */
export function omitArrayIndex(arr, index) {
const output = [...arr.slice(0, index), ...arr.slice(index + 1)];
return arr.length !== output.length ? output : arr;
}
/** Get the first item from an array or iterable, or `undefined` if it didn't exist. */
export function getFirst(items) {
if (isArray(items))
return items[0];
for (const i of items)
return i;
}
/** Get the first item from an array or iterable. */
export function requireFirst(items, caller = requireFirst) {
const item = getFirst(items);
if (item === undefined)
throw new RequiredError("First item is required", { items: items, caller });
return item;
}
/** Get the last item from an array or iterable, or `undefined` if it didn't exist. */
export function getLast(items) {
if (isArray(items))
return items[items.length - 1];
let last;
for (const i of items) {
last = i;
}
return last;
}
/** Get the last item from an array or iterable. */
export function requireLast(items, caller = requireLast) {
const item = getLast(items);
if (item === undefined)
throw new RequiredError("Last item is required", { items, caller });
return item;
}
/** Get the next item in an array or iterable. */
export function getNext(items, item) {
let found = false;
for (const i of items) {
if (found)
return i;
if (i === item)
found = true;
}
}
/** Get the next item from an array or iterable. */
export function requireNext(items, item, caller = requireNext) {
const next = getNext(items, item);
if (next === undefined)
throw new RequiredError("Next item is required", { item, items, caller });
return next;
}
/** Get the previous item in an array or iterable. */
export function getPrev(items, value) {
let last;
for (const i of items) {
if (i === value)
return last;
last = i;
}
}
/** Get the previous item from an array or iterable. */
export function requirePrev(items, item, caller = requirePrev) {
const prev = getPrev(items, item);
if (prev === undefined)
throw new RequiredError("Previous item is required", { item, items, caller });
return prev;
}