utils.js

import {
  Iterable,
  List,
  Set
} from 'immutable';

import {
  deepPick
} from './pick';

/**
 * @module utils
 */

/**
 * Accepts an `Array`, `List` or `null` and returns an equivalent `List`. Passing in `null` will return an empty `List`.
 *
 * @example
 * asList(null); // returns List()
 * asList([0,1,2]); // returns List().of(0,1,2)
 * asList(List().of(0,1,2)); // returns List().of(0,1,2)
 *
 * @param {Array|List|null} input The input to be convert to a `List`. `List` items will pass through unchanged, all others will be passed into a `List` constructor.
 * @return {List} The equivalent `List`.
 */

function asList(input) {
    return List.isList(input) ? input : List(input);
}

/**
 * Accepts a node and returns a copy of it with all children and all non-`childPath` keys removed.
 * Essentially the minimum data structure required for the node.
 *
 * @param {*} node The node to make blank.
 * @param {ChildPath} [childPath=null] An Array or List of keys indicating where to find the node's children.
 * @return {*} The blank node.
 */

function blankNode(node, childPath = null) {
  childPath = asList(childPath);
  return node.update(deepPick(childPath));
}

/**
 * Turns a node's `nodePath` and its `childPath` into a full key path.
 *
 * @example
 * nodePathToKeys(null, ['children']); // returns []
 * nodePathToKeys(['bob'], ['children']); // returns ['children', 'bob']
 * nodePathToKeys([0,1], ['children']); // returns ['children', 0, 'children', 1]
 * 
 * @param {NodePath} nodePath A NodePath used to uniquely identify a node.
 * @param {ChildPath} [childPath=null] An Array or List of keys indicating where to find each node's children from within each node.
 * @return {List}
 */

function nodePathToKeys(nodePath, childPath = null) {
  childPath = asList(childPath);
  return asList(nodePath).reduce((fullPath, key) => {
    return fullPath.concat(childPath).push(key);
  }, List());
}

/**
 * Turns a node's nodePath and its childPath into a full key path to the node's children.
 *
 * @example
 * nodePathToKeysChildren(null, ['children']); // returns ['children']
 * nodePathToKeysChildren(['bob'], ['children']); // returns ['children', 'bob', children']
 * nodePathToKeysChildren([0,1], ['children']); // returns ['children', 0, 'children', 1, 'children']
 *
 * @param {NodePath} nodePath A NodePath used to uniquely identify a node.
 * @param {ChildPath} [childPath=null] An Array or List of keys indicating where to find each node's children from within each node.
 * @return {List}
 */

function nodePathToKeysChildren(nodePath, childPath = null) {
  childPath = asList(childPath);
  return childPath.isEmpty()
    ? nodePathToKeys(nodePath)
    : nodePathToKeys(nodePath, childPath).concat(childPath);
}

/**
 * Accepts a node and returns a boolean indicating if the node is a leaf node (i.e. it has no children).
 *
 * @example
 * const tree = fromJS({
 *   name: "root",
 *   children: [
 *     {
 *       name: "child 1",
 *       children: [
 *         {name: "grandchild 1"},
 *         {name: "grandchild 2"}
 *       ]
 *     },
 *     {
 *       name: "child 2",
 *       children: []
 *     }
 *   ]
 * });
 *
 * isLeaf(tree.getIn(['children', 0]), ['childPath']); // returns false because child 1 has children
 * isLeaf(tree.getIn(['children', 1]), ['childPath']); // returns true because child 2 has no children
 * // note that normally deepGet is recommended instead of getIn for getting nodes from a tree
 *
 * @param {*} node The node to check.
 * @param {ChildPath} [childPath=null] An Array or List of keys indicating where to find each node's children from within each node.
 * @return {boolean} A boolean indicating if the node is a leaf node.
 */

function isLeaf(node, childPath = null) {
  if(!Iterable.isIterable(node)) {
    return true;
  }
  const children = node.getIn(childPath || []);
  return !children || children.isEmpty();
}

/**
 * Filter an Immutable `Iterable` so it only retains any keys provided in the `keys` param.
 * Similar to the pick function in Lodash.
 *
 * @example
 * const map = Map({
 *   a: "A",
 *   b: "B",
 *   c: "C",
 *   d: "D"
 * });
 *
 * pick(map, ['a', 'c']); // returns Map({a: "A", c: "C"});
 *
 * @param {Iterable} iterable The `Iterable` to filter.
 * @param {Array} keys The iterable's keys to keep.
 * @return {Iterable}
 */

function pick(iterable, keys) {
  const keySet = Set(keys);
  return iterable.filter((v, k) => keySet.has(k));
}


export {
  asList,
  blankNode,
  nodePathToKeys,
  nodePathToKeysChildren,
  isLeaf,
  pick
}