utils/index.js

/** @module bdv2-theme-converter/utils */

/**
 * Renames an object's keys.
 *
 * @param {Object} obj - The source object.
 * @param {Object} newKeys
 * An object with the old keys as keys and the new keys as values.
 * @returns {Object} The source object with the keys renamed.
 */
module.exports.renameKeys = (obj, newKeys) => {
  return Object.assign({}, ...Object.keys(obj)
    .map(k => ({[(newKeys[k] || k)]: obj[k]}))
  );
};

/**
 * Merges multiple objects into one.
 * If the objects share keys, the rightmost object's value will be used.
 *
 * @param {...Object} ...obj - The objects to merge.
 * @returns {Object} The merged object.
 */
module.exports.merge = (...obj) => Object.assign({}, ...obj);

/**
 * Returns a checkmarked message.
 *
 * @param {string} msg - The original message.
 * @returns {string} The checkmarked message.
 */
module.exports.checkMark = (msg) => `  \x1b[32m\u2713\x1b[0m ${msg}`;

/**
 * Checks whether the given object is empty.
 *
 * @param {Object} obj - The object to be checked.
 * @returns {boolean}
 */
module.exports.isEmpty = (obj) => (!obj || Object.keys(obj).length === 0);

/**
 * Checks whether the given argument is an object.
 *
 * @param {*} arg - The argument to be checked.
 * @returns {boolean}
 */
module.exports.isObject = (arg) => (arg !== null && typeof arg === 'object');

/**
 * Checks whether the given argument is a finite number.
 *
 * @param {*} arg - The argument to be checked.
 * @returns {boolean}
 */
module.exports.isNumber = (arg) => {
  if(typeof arg === 'number') return isFinite(arg);
  if(!arg) return false;
  if(typeof arg === 'string' && arg.trim() !== '')
    return isFinite(Number(arg));
  return false;
};

/**
 * Returns an indent string of tabs or spaces.
 *
 * @param {string|number} type
 * Can be one of <code>tab</code> or <code>space</code>, or a number.
 * @param {number} width - The width of the indent string.
 * @throws Will throw an error on invalid type or width.
 * @returns {string} The indent string.
 */
module.exports.indent = (type, width) => {
  if(this.isNumber(type)) return ' '.repeat(parseInt(type));
  if(this.isNumber(width)) {
    if(type.match(/^tab$/i)) return '\t'.repeat(width);
    if(type.match(/^space$/i)) return ' '.repeat(width);
    throw new TypeError("Invalid indent type, expected 'tab' or 'space'");
  }
  throw new TypeError('Invalid indent width, expected a number');
};

/**
 * Removes null, undefined, and empty values from an object.
 *
 * @param {Object} obj - The object to be stripped.
 * @returns {Object} The stripped object.
 * @since 0.1.1
 */
module.exports.stripEmpty = (obj) => (
  Object.keys(obj).reduce((acc, key) => {
    const val = obj[key];
    // eslint-disable-next-line no-undefined
    const empty = ['', null, undefined];
    const isEmpty = empty.includes(val) ||
      ((Array.isArray(val) && val.length === 0) ||
      (this.isObject(val) && this.isEmpty(val)));
    if(!isEmpty) acc[key] = obj[key];
    return acc;
  }, {})
);

/**
 * Creates missing directories recursively.
 *
 * @param {string} dir - The directory path to create.
 * @see {@link https://stackoverflow.com/a/41970204|SO answer}
 * @since 0.1.2
 */
module.exports.mkdirRecursive = (dir) => {
  const {sep} = require('path');
  const fs = require('fs');
  dir.split(sep)
    .reduce((currPath, folder) => {
      currPath += folder + sep;
      if(!fs.existsSync(currPath))
        fs.mkdirSync(currPath);
      return currPath;
    }, '');
};