import { NotFoundError } from './error/NotFoundError'; import { mergeList, toList } from './List'; import { doesExist, isNil, mustExist, Optional } from './Maybe'; export interface Dict { [key: string]: TVal; } /** * A `Map` or dictionary object with string keys and `TVal` values. * * @public */ export type MapLike = Map | Dict; /** * Get an element from a Map and guard against nil values. * * @public */ export function mustGet(map: Map, key: TKey): TVal { const val = map.get(key); return mustExist(val); } /** * Get a map key or default value when the key does not exist or is nil. * * @public */ export function getOrDefault(map: Map, key: TKey, defaultValue: TVal): TVal { if (map.has(key)) { const data = map.get(key); if (doesExist(data)) { return data; } } return defaultValue; } /** * Get the first element from the specified key within a map of lists. * * @public */ export function getHead(map: Map>, key: TKey): TVal { const value = map.get(key); if (isNil(value) || value.length === 0) { throw new NotFoundError(); } return value[0]; } /** * Get the first element from the specified key, within a map of lists, or a default value when * the key does not exist or is nil. * * @public */ export function getHeadOrDefault(map: Map>>, key: TKey, defaultValue: TVal): TVal { if (!map.has(key)) { return defaultValue; } const data = map.get(key); if (isNil(data)) { return defaultValue; } const [head] = data; if (isNil(head)) { return defaultValue; } return head; } /** * Set a map key to a new array or push to the existing value. * @param map The destination map and source of existing values. * @param key The key to get and set. * @param val The value to add. * * @public */ export function setOrPush(map: Map>, key: TKey, val: TVal | ReadonlyArray) { const prev = map.get(key); if (doesExist(prev)) { map.set(key, mergeList(prev, val)); } else { map.set(key, toList(val)); } } /** * Merge the `source` map into the `target` map, replacing keys that already exist. * * @public */ export function mergeMap(target: Map, source: Map | ReadonlyArray<[TKey, TVal]>) { for (const [k, v] of source) { target.set(k, v); } return target; } /** * Merge the provided maps into a new map, merging keys that already exist by pushing new items. * * @public */ export function pushMergeMap(...args: Array>>): Map>; export function pushMergeMap(...args: ReadonlyArray>>): Map>; export function pushMergeMap(...args: ReadonlyArray>>): Map> { const out = new Map(); for (const arg of args) { for (const [key, val] of arg) { setOrPush(out, key, val); } } return out; } /** * Clone a map or map-like object into a new map. * * @public */ export function makeMap(val: Optional>): Map { // nil: empty map if (isNil(val)) { return new Map(); } // already a map: make a copy if (val instanceof Map) { return new Map(val.entries()); } // otherwise: dict return new Map(Object.entries(val)); } /** * Turns a map or dict into a dict * * @public */ export function makeDict(map: Optional>): Dict { if (isNil(map)) { return {}; } if (map instanceof Map) { const result: Dict = {}; for (const [key, val] of map) { result[key] = val; } return result; } return map; } export interface NameValuePair { name: string; value: TVal; } /** * Turns a list of name-value pairs into a map. * * @public */ export function pairsToMap(pairs: ReadonlyArray>): Map { const map = new Map(); for (const p of pairs) { map.set(p.name, p.value); } return map; } export function dictValuesToArrays(map: MapLike): Dict> { const data: Dict> = {}; for (const [key, value] of makeMap(map)) { if (Array.isArray(value)) { data[key] = value; } else { data[key] = [value]; } } return data; } /** * Normalize a map-like of values into a dict of lists of strings. * * @beta */ export function normalizeMap(map: MapLike): Dict> { const data: Dict> = {}; for (const [key, value] of makeMap(map)) { if (Array.isArray(value)) { data[key] = value; } else if (typeof value === 'string') { data[key] = [value]; } else if (typeof value === 'number') { data[key] = [value.toString()]; } else if (typeof value === 'object' && doesExist(value)) { data[key] = [value.toString()]; } } return data; } /** * Get entries of a map-like. * * @public */ export function entriesOf(map: Optional>): Array<[string, TVal]> { if (map instanceof Map) { return Array.from(map.entries()); } if (map instanceof Object) { return Object.entries(map); } return []; }