Codibre/fluent-iterable

View on GitHub
src/utils/utils.ts

Summary

Maintainability
A
3 hrs
Test Coverage
A
98%
/* eslint-disable guard-for-in */
/* eslint-disable @typescript-eslint/no-empty-function */
import fluent from '../fluent';
import { FluentAsyncIterable, FluentGroup, FluentIterable } from '../types';
import { Group, AverageStepper } from '../types/base';
import { AnyIterable, AsyncPredicate } from 'augmentative-iterable';
import { orderAssured } from '../types-internal';
import { valueTypeWrapper } from '../types-internal/string-wrapper';
import { map } from '../sync/map';
import { FluentClass } from '../fluent-class';

const valueTypes = ['string', 'number', 'boolean'];
/**
 * Returns exactly the informed parameter
 * @param param The informed parameter to be returned
 */
function identity<T>(param: T): T {
  return param;
}

/**
 * @internal
 */
async function* promiseIterateAsync<T>(
  a: PromiseLike<AnyIterable<T>>,
): AsyncIterable<T> {
  const resolved = await a;
  yield* resolved;
}

function iteratorIterateAsync<T>(a: Iterable<T>) {
  return {
    [Symbol.asyncIterator]() {
      const iterator = a[Symbol.iterator]();
      return {
        next() {
          const { done, value } = iterator.next();
          return Promise.resolve({ done, value: done ? undefined : value });
        },
        return: iterator.return
          ? () => Promise.resolve(iterator.return!(iterator))
          : undefined,
        throw: iterator.throw
          ? () => Promise.resolve(iterator.throw!(iterator))
          : undefined,
      };
    },
  };
}

export function isPromise(t: unknown): t is PromiseLike<any> {
  return !!(t && typeof (t as any).then === 'function');
}

/**
 * Iterates all element of an async iterable
 * @typeparam T the item type of the [[Iterable]]
 * @param a The async iterable
 */
function iterateAsync<T>(
  a: AnyIterable<T> | PromiseLike<AnyIterable<T>>,
): AsyncIterable<T> {
  if (isPromise(a)) return promiseIterateAsync(a as any);
  return (a as any)[Symbol.iterator]
    ? iteratorIterateAsync(a as any)
    : (a as any);
}

/**
 * Iterates in all elements of an async iterable of iterables or async iterables
 * @typeparam T the item type of the internal [[Iterable/AsyncIterable]]
 * @param a The async iterable
 */
async function* iterateAllAsync<T>(a: AsyncIterable<AnyIterable<T>>) {
  for await (const it of a) {
    yield* it;
  }
}

/**
 * Iterates all element of an iterable
 * @typeparam T the item type of the [[Iterable]]
 * @param a The iterable
 */
const iterate: <T>(a: Iterable<T>) => Iterable<T> = identity;

/**
 * Returns a function that always returns the informed value
 * @param value the constant value
 */
function constant<T>(value: T) {
  return function constantValue() {
    return value;
  };
}

/**
 * Iterates in all elements of an iterable of iterables
 * @typeparam T the item type of the internal [[Iterable]]
 * @param a The iterable
 */
function* iterateAll<T>(a: Iterable<Iterable<T>>) {
  for (const it of a) {
    yield* it;
  }
}

/**
 * Iterates over all owned properties of the given object
 * @param obj The object to iterate with
 */
function* iterateObjProps<T extends object>(obj: T): Iterable<keyof T> {
  for (const property in obj) {
    yield property;
  }
}

/**
 * Iterates over all owned entries of given object
 * @param obj The object to iterate with
 */
function iterateObjEntries<
  T extends object,
  K extends keyof T = keyof T,
  V extends T[K] = T[K],
>(obj: T): Iterable<[K, V]> {
  return map.call(iterateObjProps(obj), (property) => [
    property,
    obj[property as K],
  ]) as Iterable<[K, V]>;
}
/**
 * Provides a "equals" comparer
 * @typeparam T the type of b
 * @param b the value for comparison
 */
function eq<T>(b: any) {
  return (a: T) => a === b;
}

/**
 * Provides a "greater than" comparer
 * @typeparam T the type of b
 * @param b the value for comparison
 */
function gt<T>(b: any) {
  return (a: T) => a > b;
}

/**
 * Provides a "greater or equal" comparer
 * @typeparam T the type of b
 * @param b the value for comparison
 */
function ge<T>(b: any) {
  return (a: T) => a >= b;
}

/**
 * Provides a "lesser than" comparer
 * @typeparam T the type of b
 * @param b the value for comparison
 */
function lt<T>(b: any) {
  return (a: T) => a < b;
}

/**
 * Provides a "lesser or equal" comparer
 * @typeparam T the type of b
 * @param b the value for comparison
 */
function le<T>(b: any) {
  return (a: T) => a <= b;
}

/**
 * Provides an empty iterable
 */
function* empty(): Iterable<undefined> {}

/**
 * Provides an empty async iterable
 */
async function* emptyAsync(): AsyncIterable<undefined> {}

/**
 * Always returns true
 */
function truth(): boolean {
  return true;
}

/**
 * Always returns false
 */
function falsity(): boolean {
  return false;
}

/**
 * Provides a function that negates the informed predicate
 * @typeparam T the item type of the [[Predicate]]
 * @param predicate The predicate to be negated
 */
function negation<TArgs extends any[], R>(
  this: unknown,
  predicate: (...args: TArgs) => R,
): (...args: TArgs) => boolean {
  return (...item: TArgs) => !predicate.apply(this, item);
}

/**
 * Provides a function that negates the informed async predicate
 * @typeparam T the item type of the [[AsyncPredicate]]
 * @param predicate The async predicate to be negated
 */
function asyncNegation<T>(predicate: AsyncPredicate<T>): AsyncPredicate<T> {
  return async (item: T) => !(await predicate(item));
}

/**
 * Convert a simple [[Group]] to a [[FluentGroup]]
 * @typeparam Key The type of the key
 * @typeparam Value the type of the items of the value property
 * @param {Group} grp the [[Group]] to be converted
 */
function fluentGroup<Key, Value>(
  grp: Group<Value, Key>,
): FluentGroup<Value, Key> {
  return {
    ...grp,
    values: fluent(grp.values),
  };
}

/**
 * Returns an object to calculates incremental average/iterative means
 */
function getAverageStepper() {
  let avg = 0;
  let count = 0;
  const wrapper: AverageStepper = {
    get avg() {
      return count ? avg : NaN;
    },
    get count() {
      return count;
    },
    step: (y: number) => (avg = avg + (y - avg) / ++count),
  };

  return wrapper;
}
function getItemToAssure<
  F extends Function | FluentIterable<any> | FluentAsyncIterable<any>,
>(f: F): any {
  return typeof f === 'function' && !(f instanceof FluentClass)
    ? (...args: any[]) => (f as CallableFunction)(...args)
    : f;
}

/**
 * Returns a new instance of a function with a order assuring mark.
 * Fluent Iterable will treat order Assuring marked function as if
 * they're guaranteed to return ordered result in regard some iterable
 * where they're applied. The actual order, though, is of responsibility
 * of the code using this package.
 *
 * This is useful to have access to faster versions of some algorithms, but
 * the output may not match expectation if the resulting order is not actually right.
 *
 * @param f the function to assure order
 */
function assureOrder<
  F extends Function | FluentIterable<any> | FluentAsyncIterable<any>,
>(f: F): F {
  const result = getItemToAssure(f);
  result[orderAssured] = 1;
  return result;
}

/**
 * Returns a new instance of a function with a descending order assuring mark.
 * Fluent Iterable will treat descending order assuring marked functions as if
 * they're guaranteed to return descending ordered results in regard some iterable
 * where they're applied. The actual order, though, is of responsibility
 * of the code using this package.
 *
 * This is useful to have access to faster versions of some algorithms, but
 * the output may not match expectation if the resulting order is not actually right.
 *
 * @param f the function to assure order
 */
function assureOrderDescending<
  F extends Function | FluentIterable<any> | FluentAsyncIterable<any>,
>(f: F): F {
  const result = getItemToAssure(f);
  result[orderAssured] = -1;
  return result;
}

function isValueType(f: any) {
  const tp = typeof f;
  return valueTypes.includes(tp);
}

/**
 * Mark a field name or a mapper as ascending, for use with sortBy
 * @param f the mapper or the field name
 */
export function asc<F>(f: F): F {
  return assureOrder((isValueType(f) ? { [valueTypeWrapper]: f } : f) as any);
}

/**
 * Mark a field name or a mapper as descending, for use with sortBy
 * @param f the mapper or the field name
 */
export function desc<F>(f: F): F {
  return assureOrderDescending(
    (isValueType(f) ? { [valueTypeWrapper]: f } : f) as any,
  );
}

export {
  assureOrder,
  assureOrderDescending,
  constant,
  empty,
  emptyAsync,
  identity,
  truth,
  falsity,
  negation,
  asyncNegation,
  fluentGroup,
  getAverageStepper,
  eq,
  ge,
  gt,
  le,
  lt,
  iterateAsync,
  iterateAllAsync,
  iterate,
  iterateAll,
  iterateObjProps,
  iterateObjEntries,
};