acuminous/power-merge

View on GitHub
index.js

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
const R = require('ramda');
const asyncify = require('async.asyncify');
const variadic = require('variadic');
const path = require('path');
const commands = require('require-all')({ dirname: path.join(__dirname, 'lib', 'commands') });
const ruleSets = require('require-all')({ dirname: path.join(__dirname, 'lib', 'rulesets') });

const Context = require('./lib/Context');
const defaults = require('./lib/defaults');
const noop = require('./lib/noop');

function compile(_options, namedCommands) {
  const options = withDefaultOptions(_options);
  const context = new Context({ namedCommands, options });
  const rules = preProcessRules(context, options.rules);
  return buildMerge(context, rules);
}

function withDefaultOptions(options) {
  return R.mergeDeepLeft(options || {}, defaults);
}

function preProcessRules(context, rules) {
  return R.compose(R.map((rule) => ({
    when: rule.when ? rule.when(context) : commands.always(context),
    then: rule.then(context)
  })), R.flatten)(rules.concat(ruleSets.errorOnNoMatchingRules));
}

function buildMerge(context, rules) {
  const partial = R.curry(merge)(context, rules);
  context.set('merge', partial);
  return withApiWrapper(partial, context);
}

function withApiWrapper(fn, context) {
  fn = withResetWrapper(fn, context);
  const api = context.get('options').api;
  if (api.direction === 'right-to-left') fn = R.compose(fn, R.reverse);
  if (api.variadic) fn = variadic(fn);
  if (api.async) fn = asyncify(fn);
  return fn;
}

function withResetWrapper(fn, context) {
  return function(args) {
    context.reset();
    return fn(args);
  };
}

function merge(context, rules, args) {
  if (args.length === 0) return;
  if (args.length === 1) return args[0]; // To clone or not to clone

  let a = args[0];
  for (let i = 1; i < args.length; i++) {
    const b = args[i];
    for (let r = 0; r < rules.length; r++) {
      const rule = rules[r];
      const node = context.get('node');
      const facts = {
        a: { value: a, type: R.type(a) },
        b: { value: b, type: R.type(b) },
        node: node.getFacts()
      };
      facts.a.circular = context.isCircular(facts.a);
      facts.b.circular = context.isCircular(facts.b);
      if (!rule.when(facts)) continue;
      context.recordHistory(facts.a);
      context.recordHistory(facts.b);
      a = rule.then(facts);
      context.eraseHistory(facts.a);
      context.eraseHistory(facts.b);
      break;
    }
  }
  return a;
}

module.exports = { compile, noop, commands, ruleSets };