<commit_before>var svg = require('simplesvg');
var hammer = require('hammerjs');

var MOVE_EVENTS = 'panstart panmove panend';

module.exports = ngraphSvg;

function ngraphSvg(graph, settings) {
  settings = settings || {};

  var layout = require('./lib/defaultLayout.js')(graph, settings);

  var container = settings.container || document.body;

  var isStable = false;
  var disposed = false;
  var sceneInitialized = false;

  var svgRoot = createSvgRoot(container);
  var sceneRoot = createSceneRoot(svgRoot);
  var sceneTransform = createSceneTransform(sceneRoot);
  var panSession = {};
  var panNode = 0;

  var linkLayer = addLayer('links', sceneRoot);
  var nodeLayer = addLayer('nodes', sceneRoot);

  var nodes = Object.create(null);
  var links = Object.create(null);

  var fromX = 0, fromY = 0;
  var screenPinchX, screenPinchY, lastScale;
  var cachedPos = { x: 0, y: 0 },
      cachedFromPos = { x: 0, y: 0 },
      cachedToPos = { x: 0, y: 0 };

  var defaultUI = require('./lib/defaultUI.js');
  var nodeBuilder = defaultUI.nodeBuilder,
    nodePositionCallback = defaultUI.nodePositionCallback,
    linkBuilder = defaultUI.linkBuilder,
    linkPositionCallback = defaultUI.linkPositionCallback;

  var currentTransform = {
    tx : 0,
    ty : 0,
    scale: 1
  };

  var api = {
    run: animationLoop,
    renderOneFrame: renderOneFrame,
    layout: layout,

    dispose: dispose,

    node: setNodeBuilder,

    link: setLinkBuilder,

    placeNode: placeNode,

    placeLink: placeLink,

    svgRoot: svgRoot
  };

  require('ngraph.events')(api);

  return api;

  function dispose() {
      layout.dispose();
      api.off();
      disposed = true;
      listenToGraphEvents(false);
      //listenToDomEvents(false);
    }

  function placeLink(newPlaceLinkCallback) {
    linkPositionCallback = newPlaceLinkCallback;
    return api;
  }

  function placeNode(newPlaceCallback) {
    nodePositionCallback = newPlaceCallback;
    return api;
  }

  function animationLoop() {
    if (disposed) return;
    requestAnimationFrame(animationLoop);

    if (!isStable) {
      isStable = layout.step();
      renderOneFrame();
    }
  }

  function renderOneFrame() {
    if (disposed) return;
    if (!sceneInitialized) initializeScene();

    for (var nodeId in nodes) {
      var nodeInfo = nodes[nodeId];
      cachedPos.x = nodeInfo.pos.x;
      cachedPos.y = nodeInfo.pos.y;
      nodePositionCallback(nodeInfo.ui, cachedPos, nodeInfo.model);
    }

    for (var linkId in links) {
      var linkInfo = links[linkId];
      cachedFromPos.x = linkInfo.pos.from.x;
      cachedFromPos.y = linkInfo.pos.from.y;
      cachedToPos.x = linkInfo.pos.to.x;
      cachedToPos.y = linkInfo.pos.to.y;
      linkPositionCallback(linkInfo.ui, cachedFromPos, cachedToPos, linkInfo.model);
    }
  }

  function initializeScene() {
    graph.forEachNode(addNode);
    graph.forEachLink(addLink);

    moveTo(container.clientWidth / 2, container.clientHeight / 2);

    listenToGraphEvents(true);
    sceneInitialized = true;
  }

  function addLink(link) {
    var linkUI = linkBuilder(link);
    if (!linkUI) throw new Error('Link builder is supposed to return SVG object');

    links[link.id] = {
      pos: layout.getLinkPosition(link.id),
      model: link,
      ui: linkUI
    };

    linkLayer.append(linkUI);
  }

  function addNode(node) {
    var ui = nodeBuilder(node);
    if (!ui) throw new Error('Node builder is supposed to return SVG object');

    nodeLayer.append(ui);

    var pos = layout.getNodePosition(node.id);
    var nodeDescriptor = {
      pos: pos,
      model: node,
      ui: ui
    };

    var recognizers = { recognizers: [
      [hammer.Pan, { threshold: 1 }]
    ] };
    nodeDescriptor.events = hammer(ui, recognizers).on(MOVE_EVENTS, onNodePan(pos, node));
    nodes[node.id] = nodeDescriptor;
  }

  function setLinkBuilder(builderCallback) {
    if (typeof builderCallback !== "function") throw new Error('link builder callback is supposed to be a function');

    linkBuilder = builderCallback; // todo: rebuild all nodes?
    return api;
  }

  function setNodeBuilder(builderCallback) {
    if (typeof builderCallback !== "function") throw new Error('node builder callback is supposed to be a function');

    nodeBuilder = builderCallback; // todo: rebuild all nodes?
    return api;
  }

  function listenToGraphEvents(isOn) {
    graph[isOn ? 'on' : 'off']('changed', onGraphChanged);
  }

  function onGraphChanged(changes) {
    resetStable();

    for (var i = 0; i < changes.length; ++i) {
      var change = changes[i];
      if (change.changeType === 'add') {
        if (change.node) {
          addNode(change.node);
        }
        if (change.link) {
          addLink(change.link);
        }
      } else if (change.changeType === 'remove') {
        if (change.node) {
          removeNode(change.node);
        }
        if (change.link) {
          removeLink(change.link);
        }
      }
    }
  }

  function resetStable() {
    isStable = false;
  }

  function createSvgRoot(element) {
    if (element instanceof SVGSVGElement) return element;
    var svgRoot = svg("svg");
    element.appendChild(svgRoot);

    return svgRoot;
  }

  function createSceneRoot(svgRoot) {
    var scene = svg('g').attr("buffered-rendering", "dynamic");
    svgRoot.append(scene);

    var sceneMoveRecognizer = { recognizers: [
      [hammer.Pan, { threshold: 1 }],
      [hammer.Pinch, { enable: true }]
    ] };

    // somehow ios does not fire events on svg. Use body instead:
    hammer(document.body, sceneMoveRecognizer)
      .on(MOVE_EVENTS, onScenePan)
      .on('pinchstart pinchin pinchout', onScreenPinch);

    var addWheelListener = require('wheel');
    addWheelListener(svgRoot, onWheel);
    return scene;
  }

  function onScenePan(e) {
    if (e.target !== svgRoot || panNode > 0) return;
    if (e.type === 'panmove') {
      currentTransform.tx = fromX + e.deltaX;
      currentTransform.ty = fromY + e.deltaY;
      updateTransformMatrix();
    } else if (e.type === 'panstart') {
      fromX = currentTransform.tx;
      fromY = currentTransform.ty;
    }
  }

  function onWheel(e) {
    var isZoomIn = e.deltaY < 0;
    var direction = isZoomIn ? 1 : -1;
    var factor = (1 + direction * 0.1);
    zoomTo(e.clientX, e.clientY, factor);
  }

  function zoomTo(x, y, factor) {
    currentTransform.tx = x - factor * (x - currentTransform.tx);
    currentTransform.ty = y - factor * (y - currentTransform.ty);
    currentTransform.scale *= factor;
    updateTransformMatrix();
  }

  function addLayer(name, parent) {
    var layer = svg('g').attr('id', name);
    parent.append(layer);
    return layer;
  }

  function getModelPosition(pos) {
    return {
      x: (pos.x - currentTransform.tx)/currentTransform.scale,
      y: (pos.y - currentTransform.ty)/currentTransform.scale,
    };
  }
  function createSceneTransform(scene) {
    var transform = svgRoot.createSVGTransform();
    scene.transform.baseVal.appendItem(transform);

    return transform;
  }

  function moveTo(x, y) {
    currentTransform.tx = x;
    currentTransform.ty = y;
    updateTransformMatrix();
  }

  function updateTransformMatrix() {
    sceneTransform.matrix.e = currentTransform.tx;
    sceneTransform.matrix.f = currentTransform.ty;
    sceneTransform.matrix.a = sceneTransform.matrix.d = currentTransform.scale;
  }

  function onNodePan(pos, model) {
    return function onNodePan(e) {
      var clickPosition = getModelPosition(e.center);

      if (e.type === 'panmove') {
        var status = panSession[model.id];
        layout.setNodePosition(model.id, clickPosition.x - status.dx , clickPosition.y - status.dy);
      } else if (e.type === 'panstart') {
        panSession[model.id] = {
          isPinned: layout.isNodePinned(model),
          dx: clickPosition.x - pos.x,
          dy: clickPosition.y - pos.y
        };
        layout.pinNode(model, true);
        panNode += 1;
      } else if (e.type === 'panend') {
        layout.pinNode(model, panSession[model.id].isPinned);
        panNode -= 1;
      }
    };
  }

  function removeNode(node) {
    var descriptor = nodes[node.id];
    if (!descriptor) return;

    descriptor.events.off(MOVE_EVENTS);

    var parent = descriptor.ui.parentNode;
    if (parent) parent.removeChild(descriptor.ui);

    delete nodes[node.id];
  }

  function removeLink(link) {
    var descriptor = links[link.id];
    if (!descriptor) return;

    var parent = descriptor.ui.parentNode;
    if (parent) parent.removeChild(descriptor.ui);

    delete links[link.id];
  }

  function onScreenPinch(e) {
    if (e.target !== svgRoot) return;

    if (e.type === 'pinchstart') {
      screenPinchX = e.center.x;
      screenPinchY = e.center.y;
      lastScale = e.scale;
    } else {
      var direction = lastScale > e.scale ? -1 : 1;
      lastScale = e.scale;
      var factor = (1 + direction * 0.04);
      zoomTo(screenPinchX, screenPinchY, factor);
    }
  }
}
<commit_msg>Reset stable when user moves ndoe<commit_after>var svg = require('simplesvg');
var hammer = require('hammerjs');

var MOVE_EVENTS = 'panstart panmove panend';

module.exports = ngraphSvg;

function ngraphSvg(graph, settings) {
  settings = settings || {};

  var layout = require('./lib/defaultLayout.js')(graph, settings);

  var container = settings.container || document.body;

  var isStable = false;
  var disposed = false;
  var sceneInitialized = false;

  var svgRoot = createSvgRoot(container);
  var sceneRoot = createSceneRoot(svgRoot);
  var sceneTransform = createSceneTransform(sceneRoot);
  var panSession = {};
  var panNode = 0;

  var linkLayer = addLayer('links', sceneRoot);
  var nodeLayer = addLayer('nodes', sceneRoot);

  var nodes = Object.create(null);
  var links = Object.create(null);

  var fromX = 0, fromY = 0;
  var screenPinchX, screenPinchY, lastScale;
  var cachedPos = { x: 0, y: 0 },
      cachedFromPos = { x: 0, y: 0 },
      cachedToPos = { x: 0, y: 0 };

  var defaultUI = require('./lib/defaultUI.js');
  var nodeBuilder = defaultUI.nodeBuilder,
    nodePositionCallback = defaultUI.nodePositionCallback,
    linkBuilder = defaultUI.linkBuilder,
    linkPositionCallback = defaultUI.linkPositionCallback;

  var currentTransform = {
    tx : 0,
    ty : 0,
    scale: 1
  };

  var api = {
    run: animationLoop,
    renderOneFrame: renderOneFrame,
    layout: layout,

    dispose: dispose,

    node: setNodeBuilder,

    link: setLinkBuilder,

    placeNode: placeNode,

    placeLink: placeLink,

    svgRoot: svgRoot
  };

  require('ngraph.events')(api);

  return api;

  function dispose() {
      layout.dispose();
      api.off();
      disposed = true;
      listenToGraphEvents(false);
      //listenToDomEvents(false);
    }

  function placeLink(newPlaceLinkCallback) {
    linkPositionCallback = newPlaceLinkCallback;
    return api;
  }

  function placeNode(newPlaceCallback) {
    nodePositionCallback = newPlaceCallback;
    return api;
  }

  function animationLoop() {
    if (disposed) return;
    requestAnimationFrame(animationLoop);

    if (!isStable) {
      isStable = layout.step();
      renderOneFrame();
    }
  }

  function renderOneFrame() {
    if (disposed) return;
    if (!sceneInitialized) initializeScene();

    for (var nodeId in nodes) {
      var nodeInfo = nodes[nodeId];
      cachedPos.x = nodeInfo.pos.x;
      cachedPos.y = nodeInfo.pos.y;
      nodePositionCallback(nodeInfo.ui, cachedPos, nodeInfo.model);
    }

    for (var linkId in links) {
      var linkInfo = links[linkId];
      cachedFromPos.x = linkInfo.pos.from.x;
      cachedFromPos.y = linkInfo.pos.from.y;
      cachedToPos.x = linkInfo.pos.to.x;
      cachedToPos.y = linkInfo.pos.to.y;
      linkPositionCallback(linkInfo.ui, cachedFromPos, cachedToPos, linkInfo.model);
    }
  }

  function initializeScene() {
    graph.forEachNode(addNode);
    graph.forEachLink(addLink);

    moveTo(container.clientWidth / 2, container.clientHeight / 2);

    listenToGraphEvents(true);
    sceneInitialized = true;
  }

  function addLink(link) {
    var linkUI = linkBuilder(link);
    if (!linkUI) throw new Error('Link builder is supposed to return SVG object');

    links[link.id] = {
      pos: layout.getLinkPosition(link.id),
      model: link,
      ui: linkUI
    };

    linkLayer.append(linkUI);
  }

  function addNode(node) {
    var ui = nodeBuilder(node);
    if (!ui) throw new Error('Node builder is supposed to return SVG object');

    nodeLayer.append(ui);

    var pos = layout.getNodePosition(node.id);
    var nodeDescriptor = {
      pos: pos,
      model: node,
      ui: ui
    };

    var recognizers = { recognizers: [
      [hammer.Pan, { threshold: 1 }]
    ] };
    nodeDescriptor.events = hammer(ui, recognizers).on(MOVE_EVENTS, onNodePan(pos, node));
    nodes[node.id] = nodeDescriptor;
  }

  function setLinkBuilder(builderCallback) {
    if (typeof builderCallback !== "function") throw new Error('link builder callback is supposed to be a function');

    linkBuilder = builderCallback; // todo: rebuild all nodes?
    return api;
  }

  function setNodeBuilder(builderCallback) {
    if (typeof builderCallback !== "function") throw new Error('node builder callback is supposed to be a function');

    nodeBuilder = builderCallback; // todo: rebuild all nodes?
    return api;
  }

  function listenToGraphEvents(isOn) {
    graph[isOn ? 'on' : 'off']('changed', onGraphChanged);
  }

  function onGraphChanged(changes) {
    resetStable();

    for (var i = 0; i < changes.length; ++i) {
      var change = changes[i];
      if (change.changeType === 'add') {
        if (change.node) {
          addNode(change.node);
        }
        if (change.link) {
          addLink(change.link);
        }
      } else if (change.changeType === 'remove') {
        if (change.node) {
          removeNode(change.node);
        }
        if (change.link) {
          removeLink(change.link);
        }
      }
    }
  }

  function resetStable() {
    isStable = false;
  }

  function createSvgRoot(element) {
    if (element instanceof SVGSVGElement) return element;
    var svgRoot = svg("svg");
    element.appendChild(svgRoot);

    return svgRoot;
  }

  function createSceneRoot(svgRoot) {
    var scene = svg('g').attr("buffered-rendering", "dynamic");
    svgRoot.append(scene);

    var sceneMoveRecognizer = { recognizers: [
      [hammer.Pan, { threshold: 1 }],
      [hammer.Pinch, { enable: true }]
    ] };

    // somehow ios does not fire events on svg. Use body instead:
    hammer(document.body, sceneMoveRecognizer)
      .on(MOVE_EVENTS, onScenePan)
      .on('pinchstart pinchin pinchout', onScreenPinch);

    var addWheelListener = require('wheel');
    addWheelListener(svgRoot, onWheel);
    return scene;
  }

  function onScenePan(e) {
    if (e.target !== svgRoot || panNode > 0) return;
    if (e.type === 'panmove') {
      currentTransform.tx = fromX + e.deltaX;
      currentTransform.ty = fromY + e.deltaY;
      updateTransformMatrix();
    } else if (e.type === 'panstart') {
      fromX = currentTransform.tx;
      fromY = currentTransform.ty;
    }
  }

  function onWheel(e) {
    var isZoomIn = e.deltaY < 0;
    var direction = isZoomIn ? 1 : -1;
    var factor = (1 + direction * 0.1);
    zoomTo(e.clientX, e.clientY, factor);
  }

  function zoomTo(x, y, factor) {
    currentTransform.tx = x - factor * (x - currentTransform.tx);
    currentTransform.ty = y - factor * (y - currentTransform.ty);
    currentTransform.scale *= factor;
    updateTransformMatrix();
  }

  function addLayer(name, parent) {
    var layer = svg('g').attr('id', name);
    parent.append(layer);
    return layer;
  }

  function getModelPosition(pos) {
    return {
      x: (pos.x - currentTransform.tx)/currentTransform.scale,
      y: (pos.y - currentTransform.ty)/currentTransform.scale,
    };
  }
  function createSceneTransform(scene) {
    var transform = svgRoot.createSVGTransform();
    scene.transform.baseVal.appendItem(transform);

    return transform;
  }

  function moveTo(x, y) {
    currentTransform.tx = x;
    currentTransform.ty = y;
    updateTransformMatrix();
  }

  function updateTransformMatrix() {
    sceneTransform.matrix.e = currentTransform.tx;
    sceneTransform.matrix.f = currentTransform.ty;
    sceneTransform.matrix.a = sceneTransform.matrix.d = currentTransform.scale;
  }

  function onNodePan(pos, model) {
    return function onNodePan(e) {
      var clickPosition = getModelPosition(e.center);
      resetStable();

      if (e.type === 'panmove') {
        var status = panSession[model.id];
        layout.setNodePosition(model.id, clickPosition.x - status.dx , clickPosition.y - status.dy);
      } else if (e.type === 'panstart') {
        panSession[model.id] = {
          isPinned: layout.isNodePinned(model),
          dx: clickPosition.x - pos.x,
          dy: clickPosition.y - pos.y
        };
        layout.pinNode(model, true);
        panNode += 1;
      } else if (e.type === 'panend') {
        layout.pinNode(model, panSession[model.id].isPinned);
        panNode -= 1;
      }
    };
  }

  function removeNode(node) {
    var descriptor = nodes[node.id];
    if (!descriptor) return;

    descriptor.events.off(MOVE_EVENTS);

    var parent = descriptor.ui.parentNode;
    if (parent) parent.removeChild(descriptor.ui);

    delete nodes[node.id];
  }

  function removeLink(link) {
    var descriptor = links[link.id];
    if (!descriptor) return;

    var parent = descriptor.ui.parentNode;
    if (parent) parent.removeChild(descriptor.ui);

    delete links[link.id];
  }

  function onScreenPinch(e) {
    if (e.target !== svgRoot) return;

    if (e.type === 'pinchstart') {
      screenPinchX = e.center.x;
      screenPinchY = e.center.y;
      lastScale = e.scale;
    } else {
      var direction = lastScale > e.scale ? -1 : 1;
      lastScale = e.scale;
      var factor = (1 + direction * 0.04);
      zoomTo(screenPinchX, screenPinchY, factor);
    }
  }
}
<|endoftext|>


<commit_before>'use strict';
let chalk = require('chalk');

let buildPolicyString = require('./lib/utils')['buildPolicyString'];

const CONFIG_KEY = 'ember-cli-content-security-policy';

const CSP_SELF        = "'self'";
const CSP_NONE        = "'none'";
const REPORT_PATH     = '/csp-report';

const CSP_HEADER              = 'Content-Security-Policy';
const CSP_HEADER_REPORT_ONLY  = 'Content-Security-Policy-Report-Only';

const CSP_REPORT_URI          = 'report-uri';
const CSP_FRAME_ANCESTORS     = 'frame-ancestors';
const CSP_SANDBOX             = 'sandbox';

const META_UNSUPPORTED_DIRECTIVES = [
  CSP_REPORT_URI,
  CSP_FRAME_ANCESTORS,
  CSP_SANDBOX,
];

const DELIVERY_HEADER = 'header';
const DELIVERY_META = 'meta';

const STATIC_TEST_NONCE = 'abcdefg';

let unsupportedDirectives = function(policyObject) {
  return META_UNSUPPORTED_DIRECTIVES.filter(function(name) {
    return policyObject && (name in policyObject);
  });
};

// CSP has a built-in fallback mechanism. If, say, `connect-src` is not defined it
// will fall back to `default-src`. This can cause issues. An example:
//
// Developer has has defined the following policy:
// `default-src: 'self' example.com;`
// and an addon appends the connect-src entry live-reload.local the result is:
// `default-src: 'self' example.com; connect-src: live-reload.local;`
//
// After the addons change an xhr to example.com (which was previously permitted, via fallback)
// will now be rejected since it doesn't match live-reload.local.
//
// To mitigate, whenever we append to a non-existing directive we must also copy all sources from
// default-src onto the specified directive.
let appendSourceList = function(policyObject, name, sourceList) {
  let oldSourceList;
  let oldValue = policyObject[name];

  // cast string syntax into array
  if (oldValue && typeof oldValue === 'string') {
    oldValue = oldValue.split(' ');
  }

  if (oldValue !== null && typeof oldValue !== 'undefined' && !Array.isArray(oldValue)) {
    throw new Error('Unknown source list value');
  }

  if (!oldValue || oldValue.length === 0) {
    // copy default-src (see above)
    oldSourceList = policyObject['default-src'] || [];
  } else { // array
    oldSourceList = oldValue;
  }

  // do not mutate existing source list to prevent leaking state
  oldSourceList.slice().push(sourceList);
  policyObject[name] = oldSourceList.join(' ');
};

// appends directives needed for Ember CLI live reload feature to policy object
let allowLiveReload = function(policyObject) {
  let { hostname, port, ssl } = this._config.liveReload;

  ['localhost', '0.0.0.0', hostname].filter(Boolean).forEach(function(hostname) {
    let protocol = ssl ? 'wss://' : 'ws://';
    let host = hostname + ':' + port;
    appendSourceList(policyObject, 'connect-src', protocol + host);
    appendSourceList(policyObject, 'script-src', host);
  });
}

module.exports = {
  name: require('./package').name,

  serverMiddleware: function(config) {
    let app = config.app;
    let options = config.options;

    app.use((req, res, next) => {
      if (!this._config.enabled) {
        next();
        return;
      }

      let header = this._config.reportOnly ? CSP_HEADER_REPORT_ONLY : CSP_HEADER;
      // clone policy object cause config should not be mutated
      let policyObject = Object.assign({}, this._config.policy);

      // the local server will never run for production builds, so no danger in adding the nonce all the time
      // even so it's only needed if tests are executed by opening `http://localhost:4200/tests`
      if (policyObject) {
        appendSourceList(policyObject, 'script-src', "'nonce-" + STATIC_TEST_NONCE + "'");
      }

      if (this._config.liveReload.enabled) {
        allowLiveReload(policyObject, options);
      }

      // only needed for headers, since report-uri cannot be specified in meta tag
      if (header.indexOf('Report-Only') !== -1 && !('report-uri' in policyObject)) {
        let ecHost = options.host || 'localhost';
        let ecProtocol = options.ssl ? 'https://' : 'http://';
        let ecOrigin = ecProtocol + ecHost + ':' + options.port;
        appendSourceList(policyObject, 'connect-src', ecOrigin);
        policyObject['report-uri'] = ecOrigin + REPORT_PATH;
      }

      let headerValue = buildPolicyString(policyObject);

      if (!headerValue) {
        next();
        return;
      }

      // clear existing headers before setting ours
      res.removeHeader(CSP_HEADER);
      res.removeHeader(CSP_HEADER_REPORT_ONLY);
      res.setHeader(header, headerValue);

      // for Internet Explorer 11 and below (Edge support the standard header name)
      res.removeHeader('X-' + CSP_HEADER);
      res.removeHeader('X-' + CSP_HEADER_REPORT_ONLY);
      res.setHeader('X-' + header, headerValue);

      next();
    });

    let bodyParser = require('body-parser');
    app.use(REPORT_PATH, bodyParser.json({ type: 'application/csp-report' }));
    app.use(REPORT_PATH, bodyParser.json({ type: 'application/json' }));
    app.use(REPORT_PATH, function(req, res, _next) {
      // eslint-disable-next-line no-console
      console.log(chalk.red('Content Security Policy violation:') + '\n\n' + JSON.stringify(req.body, null, 2));
      res.send({ status:'ok' });
    });
  },

  contentFor: function(type, appConfig, existingContent) {
    if (!this._config.enabled) {
      return;
    }

    if (type === 'head' && this._config.delivery.indexOf(DELIVERY_META) !== -1) {
      this.ui.writeWarnLine(
        'Content Security Policy does not support report only mode if delivered via meta element. ' +
        "Either set `ENV['ember-cli-content-security-policy'].reportOnly` to `false` or remove `'meta'` " +
        "from `ENV['ember-cli-content-security-policy'].delivery`.",
        this._config.reportOnly
      );

      let policyObject = Object.assign({}, this._config.policy);

      if (policyObject && appConfig.environment === 'test') {
        appendSourceList(policyObject, 'script-src', "'nonce-" + STATIC_TEST_NONCE + "'");
      }

      if (this._config.liveReload.enabled) {
        allowLiveReload(policyObject);
      }

      // clone policy object cause config should not be mutated
      let policyString = buildPolicyString(policyObject);

      unsupportedDirectives(policyObject).forEach(function(name) {
        let msg = 'CSP delivered via meta does not support `' + name + '`, ' +
                  'per the W3C recommendation.';
        console.log(chalk.yellow(msg)); // eslint-disable-line no-console
      });

      if (!policyString) {
        // eslint-disable-next-line no-console
        console.log(chalk.yellow('CSP via meta tag enabled but no policy exist.'));
      } else {
        return '<meta http-equiv="' + CSP_HEADER + '" content="' + policyString + '">';
      }
    }

    if (type === 'test-body-footer') {
      // Add nonce to <script> tag inserted by ember-cli to assert that test file was loaded.
      existingContent.forEach((entry, index) => {
        if (/<script>\s*Ember.assert\(.*EmberENV.TESTS_FILE_LOADED\);\s*<\/script>/.test(entry)) {
          existingContent[index] = entry.replace('<script>', '<script nonce="' + STATIC_TEST_NONCE + '">');
        }
      });
    }
  },

  includedCommands: function() {
    return require('./lib/commands');
  },

  // Configuration is only available by public API in `app` passed to `included` hook.
  // We calculate configuration in `included` hook and use it in `serverMiddleware`
  // and `contentFor` hooks, which are executed later. This is necessary cause Ember CLI
  // does not provide a public API to read build time configuation (`ember-cli-build.js`)
  // yet. `this._findHost(this).options` seems to be the only reliable way to get it in
  // these hooks but is private API.
  included: function(app) {
    let environment = app.env;
    let buildConfig = app.options || {};  // ember-cli-build.js
    let runConfig = app.project.config(); // config/environment.js
    let ui = app.project.ui;

    this._config = calculateConfig(environment, buildConfig, runConfig, ui);
  },
};

function calculateConfig(environment, buildConfig, runConfig, ui) {
  let config = {
    delivery: [DELIVERY_HEADER],
    enabled: true,
    policy: {
      'default-src':  [CSP_NONE],
      'script-src':   [CSP_SELF],
      'font-src':     [CSP_SELF],
      'connect-src':  [CSP_SELF],
      'img-src':      [CSP_SELF],
      'style-src':    [CSP_SELF],
      'media-src':    [CSP_SELF],
    },
    reportOnly: true,
  };

  // testem requires frame-src to run
  if (environment === 'test') {
    config.policy['frame-src'] = CSP_SELF;
  }

  ui.writeWarnLine(
    'Configuring ember-cli-content-security-policy using `contentSecurityPolicy`, ' +
    '`contentSecurityPolicyHeader` and `contentSecurityPolicyMeta` keys in `config/environment.js` ' +
    'is deprecate and will be removed in v2.0.0. ember-cli-content-security-policy is now configured ' +
    'using `ember-cli-build.js`. Please find detailed information about new configuration options ' +
    'in addon documentation at https://github.com/rwjblue/ember-cli-content-security-policy#ember-cli-content-security-policy.',
    !runConfig.contentSecurityPolicy || !runConfig.contentSecurityPolicyHeader || !runConfig.contentSecurityPolicyMeta
  );

  // support legacy configuration options
  if (runConfig.contentSecurityPolicy) {
    // policy object is merged not replaced
    Object.assign(config.policy, runConfig.contentSecurityPolicy);
  }
  if (runConfig.contentSecurityPolicyMeta) {
    config.delivery = [DELIVERY_META];
  }
  if (runConfig.contentSecurityPolicyHeader) {
    config.reportOnly = runConfig.contentSecurityPolicyHeader !== CSP_HEADER;
  }

  // live reload configuration is required to allow the hosts used by it
  config.liveReload = {
    enabled: buildConfig.liveReload,
    host: buildConfig.liveReloadHost,
    port: buildConfig.liveReloadPort,
    ssl: buildConfig.ssl
  }

  // apply configuration
  // policy object is merged not replaced
  if (buildConfig[CONFIG_KEY]) {
    Object.keys(buildConfig[CONFIG_KEY]).forEach((key) => {
      if (key === 'policy') {
        Object.assign(config.policy, buildConfig[CONFIG_KEY].policy);
      } else {
        config[key] = buildConfig[CONFIG_KEY][key];
      }
    });
  }

  return config;
}
module.exports._calculateConfig = calculateConfig;
<commit_msg>fix: copied source list is not used<commit_after>'use strict';
let chalk = require('chalk');

let buildPolicyString = require('./lib/utils')['buildPolicyString'];

const CONFIG_KEY = 'ember-cli-content-security-policy';

const CSP_SELF        = "'self'";
const CSP_NONE        = "'none'";
const REPORT_PATH     = '/csp-report';

const CSP_HEADER              = 'Content-Security-Policy';
const CSP_HEADER_REPORT_ONLY  = 'Content-Security-Policy-Report-Only';

const CSP_REPORT_URI          = 'report-uri';
const CSP_FRAME_ANCESTORS     = 'frame-ancestors';
const CSP_SANDBOX             = 'sandbox';

const META_UNSUPPORTED_DIRECTIVES = [
  CSP_REPORT_URI,
  CSP_FRAME_ANCESTORS,
  CSP_SANDBOX,
];

const DELIVERY_HEADER = 'header';
const DELIVERY_META = 'meta';

const STATIC_TEST_NONCE = 'abcdefg';

let unsupportedDirectives = function(policyObject) {
  return META_UNSUPPORTED_DIRECTIVES.filter(function(name) {
    return policyObject && (name in policyObject);
  });
};

// CSP has a built-in fallback mechanism. If, say, `connect-src` is not defined it
// will fall back to `default-src`. This can cause issues. An example:
//
// Developer has has defined the following policy:
// `default-src: 'self' example.com;`
// and an addon appends the connect-src entry live-reload.local the result is:
// `default-src: 'self' example.com; connect-src: live-reload.local;`
//
// After the addons change an xhr to example.com (which was previously permitted, via fallback)
// will now be rejected since it doesn't match live-reload.local.
//
// To mitigate, whenever we append to a non-existing directive we must also copy all sources from
// default-src onto the specified directive.
let appendSourceList = function(policyObject, name, sourceList) {
  let oldSourceList;
  let oldValue = policyObject[name];

  // cast string syntax into array
  if (oldValue && typeof oldValue === 'string') {
    oldValue = oldValue.split(' ');
  }

  if (oldValue !== null && typeof oldValue !== 'undefined' && !Array.isArray(oldValue)) {
    throw new Error('Unknown source list value');
  }

  if (!oldValue || oldValue.length === 0) {
    // copy default-src (see above)
    oldSourceList = policyObject['default-src'] || [];
  } else { // array
    oldSourceList = oldValue;
  }

  // do not mutate existing source list to prevent leaking state between different hooks
  let newSourceList = oldSourceList.slice().push(sourceList);
  policyObject[name] = newSourceList.join(' ');
};

// appends directives needed for Ember CLI live reload feature to policy object
let allowLiveReload = function(policyObject) {
  let { hostname, port, ssl } = this._config.liveReload;

  ['localhost', '0.0.0.0', hostname].filter(Boolean).forEach(function(hostname) {
    let protocol = ssl ? 'wss://' : 'ws://';
    let host = hostname + ':' + port;
    appendSourceList(policyObject, 'connect-src', protocol + host);
    appendSourceList(policyObject, 'script-src', host);
  });
}

module.exports = {
  name: require('./package').name,

  serverMiddleware: function(config) {
    let app = config.app;
    let options = config.options;

    app.use((req, res, next) => {
      if (!this._config.enabled) {
        next();
        return;
      }

      let header = this._config.reportOnly ? CSP_HEADER_REPORT_ONLY : CSP_HEADER;
      // clone policy object cause config should not be mutated
      let policyObject = Object.assign({}, this._config.policy);

      // the local server will never run for production builds, so no danger in adding the nonce all the time
      // even so it's only needed if tests are executed by opening `http://localhost:4200/tests`
      if (policyObject) {
        appendSourceList(policyObject, 'script-src', "'nonce-" + STATIC_TEST_NONCE + "'");
      }

      if (this._config.liveReload.enabled) {
        allowLiveReload(policyObject, options);
      }

      // only needed for headers, since report-uri cannot be specified in meta tag
      if (header.indexOf('Report-Only') !== -1 && !('report-uri' in policyObject)) {
        let ecHost = options.host || 'localhost';
        let ecProtocol = options.ssl ? 'https://' : 'http://';
        let ecOrigin = ecProtocol + ecHost + ':' + options.port;
        appendSourceList(policyObject, 'connect-src', ecOrigin);
        policyObject['report-uri'] = ecOrigin + REPORT_PATH;
      }

      let headerValue = buildPolicyString(policyObject);

      if (!headerValue) {
        next();
        return;
      }

      // clear existing headers before setting ours
      res.removeHeader(CSP_HEADER);
      res.removeHeader(CSP_HEADER_REPORT_ONLY);
      res.setHeader(header, headerValue);

      // for Internet Explorer 11 and below (Edge support the standard header name)
      res.removeHeader('X-' + CSP_HEADER);
      res.removeHeader('X-' + CSP_HEADER_REPORT_ONLY);
      res.setHeader('X-' + header, headerValue);

      next();
    });

    let bodyParser = require('body-parser');
    app.use(REPORT_PATH, bodyParser.json({ type: 'application/csp-report' }));
    app.use(REPORT_PATH, bodyParser.json({ type: 'application/json' }));
    app.use(REPORT_PATH, function(req, res, _next) {
      // eslint-disable-next-line no-console
      console.log(chalk.red('Content Security Policy violation:') + '\n\n' + JSON.stringify(req.body, null, 2));
      res.send({ status:'ok' });
    });
  },

  contentFor: function(type, appConfig, existingContent) {
    if (!this._config.enabled) {
      return;
    }

    if (type === 'head' && this._config.delivery.indexOf(DELIVERY_META) !== -1) {
      this.ui.writeWarnLine(
        'Content Security Policy does not support report only mode if delivered via meta element. ' +
        "Either set `ENV['ember-cli-content-security-policy'].reportOnly` to `false` or remove `'meta'` " +
        "from `ENV['ember-cli-content-security-policy'].delivery`.",
        this._config.reportOnly
      );

      let policyObject = Object.assign({}, this._config.policy);

      if (policyObject && appConfig.environment === 'test') {
        appendSourceList(policyObject, 'script-src', "'nonce-" + STATIC_TEST_NONCE + "'");
      }

      if (this._config.liveReload.enabled) {
        allowLiveReload(policyObject);
      }

      // clone policy object cause config should not be mutated
      let policyString = buildPolicyString(policyObject);

      unsupportedDirectives(policyObject).forEach(function(name) {
        let msg = 'CSP delivered via meta does not support `' + name + '`, ' +
                  'per the W3C recommendation.';
        console.log(chalk.yellow(msg)); // eslint-disable-line no-console
      });

      if (!policyString) {
        // eslint-disable-next-line no-console
        console.log(chalk.yellow('CSP via meta tag enabled but no policy exist.'));
      } else {
        return '<meta http-equiv="' + CSP_HEADER + '" content="' + policyString + '">';
      }
    }

    if (type === 'test-body-footer') {
      // Add nonce to <script> tag inserted by ember-cli to assert that test file was loaded.
      existingContent.forEach((entry, index) => {
        if (/<script>\s*Ember.assert\(.*EmberENV.TESTS_FILE_LOADED\);\s*<\/script>/.test(entry)) {
          existingContent[index] = entry.replace('<script>', '<script nonce="' + STATIC_TEST_NONCE + '">');
        }
      });
    }
  },

  includedCommands: function() {
    return require('./lib/commands');
  },

  // Configuration is only available by public API in `app` passed to `included` hook.
  // We calculate configuration in `included` hook and use it in `serverMiddleware`
  // and `contentFor` hooks, which are executed later. This is necessary cause Ember CLI
  // does not provide a public API to read build time configuation (`ember-cli-build.js`)
  // yet. `this._findHost(this).options` seems to be the only reliable way to get it in
  // these hooks but is private API.
  included: function(app) {
    let environment = app.env;
    let buildConfig = app.options || {};  // ember-cli-build.js
    let runConfig = app.project.config(); // config/environment.js
    let ui = app.project.ui;

    this._config = calculateConfig(environment, buildConfig, runConfig, ui);
  },
};

function calculateConfig(environment, buildConfig, runConfig, ui) {
  let config = {
    delivery: [DELIVERY_HEADER],
    enabled: true,
    policy: {
      'default-src':  [CSP_NONE],
      'script-src':   [CSP_SELF],
      'font-src':     [CSP_SELF],
      'connect-src':  [CSP_SELF],
      'img-src':      [CSP_SELF],
      'style-src':    [CSP_SELF],
      'media-src':    [CSP_SELF],
    },
    reportOnly: true,
  };

  // testem requires frame-src to run
  if (environment === 'test') {
    config.policy['frame-src'] = CSP_SELF;
  }

  ui.writeWarnLine(
    'Configuring ember-cli-content-security-policy using `contentSecurityPolicy`, ' +
    '`contentSecurityPolicyHeader` and `contentSecurityPolicyMeta` keys in `config/environment.js` ' +
    'is deprecate and will be removed in v2.0.0. ember-cli-content-security-policy is now configured ' +
    'using `ember-cli-build.js`. Please find detailed information about new configuration options ' +
    'in addon documentation at https://github.com/rwjblue/ember-cli-content-security-policy#ember-cli-content-security-policy.',
    !runConfig.contentSecurityPolicy || !runConfig.contentSecurityPolicyHeader || !runConfig.contentSecurityPolicyMeta
  );

  // support legacy configuration options
  if (runConfig.contentSecurityPolicy) {
    // policy object is merged not replaced
    Object.assign(config.policy, runConfig.contentSecurityPolicy);
  }
  if (runConfig.contentSecurityPolicyMeta) {
    config.delivery = [DELIVERY_META];
  }
  if (runConfig.contentSecurityPolicyHeader) {
    config.reportOnly = runConfig.contentSecurityPolicyHeader !== CSP_HEADER;
  }

  // live reload configuration is required to allow the hosts used by it
  config.liveReload = {
    enabled: buildConfig.liveReload,
    host: buildConfig.liveReloadHost,
    port: buildConfig.liveReloadPort,
    ssl: buildConfig.ssl
  }

  // apply configuration
  // policy object is merged not replaced
  if (buildConfig[CONFIG_KEY]) {
    Object.keys(buildConfig[CONFIG_KEY]).forEach((key) => {
      if (key === 'policy') {
        Object.assign(config.policy, buildConfig[CONFIG_KEY].policy);
      } else {
        config[key] = buildConfig[CONFIG_KEY][key];
      }
    });
  }

  return config;
}
module.exports._calculateConfig = calculateConfig;
<|endoftext|>


<commit_before>function truncateTextNode (
  textNode,
  rootElement,
  maximumHeight,
  ellipsisCharacter
) {
  var lastIndexOfWhitespace
  var textContent = textNode.textContent
  while (textContent.length > 1) {
    lastIndexOfWhitespace = textContent.lastIndexOf(' ')
    if (lastIndexOfWhitespace === -1) {
      break
    }
    textNode.textContent = textContent.substring(0, lastIndexOfWhitespace)
    if (rootElement.scrollHeight <= maximumHeight) {
      textNode.textContent = textContent
      break
    }
    textContent = textNode.textContent
  }
  return truncateTextNodeByCharacter(
    textNode,
    rootElement,
    maximumHeight,
    ellipsisCharacter
  )
}

var TRAILING_WHITESPACE_AND_PUNCTUATION_REGEX = /[ .,;!?'‘’“”\-–—]+$/
function truncateTextNodeByCharacter (
  textNode,
  rootElement,
  maximumHeight,
  ellipsisCharacter
) {
  var textContent = textNode.textContent
  var length = textContent.length
  while (length > 1) {
    // Trim off one trailing character and any trailing punctuation and whitespace.
    textContent = textContent
      .substring(0, length - 1)
      .replace(TRAILING_WHITESPACE_AND_PUNCTUATION_REGEX, '')
    length = textContent.length
    textNode.textContent = textContent + ellipsisCharacter
    if (rootElement.scrollHeight <= maximumHeight) {
      return true
    }
  }
  return false
}

function truncateElementNode (
  element,
  rootElement,
  maximumHeight,
  ellipsisCharacter
) {
  var childNodes = element.childNodes
  var i = childNodes.length - 1
  while (i > -1) {
    var childNode = childNodes[i--]
    var nodeType = childNode.nodeType
    if (
      (nodeType === 1 &&
        truncateElementNode(
          childNode,
          rootElement,
          maximumHeight,
          ellipsisCharacter
        )) ||
      (nodeType === 3 &&
        truncateTextNode(
          childNode,
          rootElement,
          maximumHeight,
          ellipsisCharacter
        ))
    ) {
      return true
    }
    element.removeChild(childNode)
  }
  return false
}

var ELLIPSIS_CHARACTER = '\u2026'

module.exports = function (rootElement, lineCount, options) {
  rootElement.style.cssText +=
    'overflow:hidden;overflow-wrap:break-word;word-wrap:break-word'

  var maximumHeight =
    (lineCount || 1) *
    parseInt(window.getComputedStyle(rootElement).lineHeight, 10)

  // Exit if text does not overflow `rootElement`.
  if (rootElement.scrollHeight <= maximumHeight) {
    return false
  }

  return truncateElementNode(
    rootElement,
    rootElement,
    maximumHeight,
    (options && options.ellipsis) || ELLIPSIS_CHARACTER
  )
}
<commit_msg>Modernise syntax<commit_after>const ELLIPSIS_CHARACTER = '\u2026'

function lineClamp (rootElement, lineCount, options) {
  rootElement.style.cssText +=
    'overflow:hidden;overflow-wrap:break-word;word-wrap:break-word'

  const maximumHeight =
    (lineCount || 1) *
    parseInt(window.getComputedStyle(rootElement).lineHeight, 10)

  // Exit if text does not overflow `rootElement`.
  if (rootElement.scrollHeight <= maximumHeight) {
    return false
  }

  return truncateElementNode(
    rootElement,
    rootElement,
    maximumHeight,
    (options && options.ellipsis) || ELLIPSIS_CHARACTER
  )
}

function truncateElementNode (
  element,
  rootElement,
  maximumHeight,
  ellipsisCharacter
) {
  const childNodes = element.childNodes
  let i = childNodes.length - 1
  while (i > -1) {
    const childNode = childNodes[i--]
    if (
      (childNode.nodeType === 1 &&
        truncateElementNode(
          childNode,
          rootElement,
          maximumHeight,
          ellipsisCharacter
        )) ||
      (childNode.nodeType === 3 &&
        truncateTextNode(
          childNode,
          rootElement,
          maximumHeight,
          ellipsisCharacter
        ))
    ) {
      return true
    }
    element.removeChild(childNode)
  }
  return false
}

function truncateTextNode (
  textNode,
  rootElement,
  maximumHeight,
  ellipsisCharacter
) {
  let lastIndexOfWhitespace
  let textContent = textNode.textContent
  while (textContent.length > 1) {
    lastIndexOfWhitespace = textContent.lastIndexOf(' ')
    if (lastIndexOfWhitespace === -1) {
      break
    }
    textNode.textContent = textContent.substring(0, lastIndexOfWhitespace)
    if (rootElement.scrollHeight <= maximumHeight) {
      textNode.textContent = textContent
      break
    }
    textContent = textNode.textContent
  }
  return truncateTextNodeByCharacter(
    textNode,
    rootElement,
    maximumHeight,
    ellipsisCharacter
  )
}

const TRAILING_WHITESPACE_AND_PUNCTUATION_REGEX = /[ .,;!?'‘’“”\-–—]+$/

function truncateTextNodeByCharacter (
  textNode,
  rootElement,
  maximumHeight,
  ellipsisCharacter
) {
  let textContent = textNode.textContent
  let length = textContent.length
  while (length > 1) {
    // Trim off one trailing character and any trailing punctuation and whitespace.
    textContent = textContent
      .substring(0, length - 1)
      .replace(TRAILING_WHITESPACE_AND_PUNCTUATION_REGEX, '')
    length = textContent.length
    textNode.textContent = textContent + ellipsisCharacter
    if (rootElement.scrollHeight <= maximumHeight) {
      return true
    }
  }
  return false
}

module.exports = lineClamp
<|endoftext|>


<commit_before>/**
 * Module dependencies
 */

var envs = require('envs');
var write = require('fs').writeFileSync;
var list = require('fs').readdirSync;
var join = require('path').join;
var resolve = require('path').resolve;
var Path = require('path');
var glob = require('glob');
var byExtension = require('./lib/loaders-by-extension');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
// polyfill Promises
require('es6-promise').polyfill();
var OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin');
var ResolveSelf = require('./lib/resolve-self');
// remove this once webpack releases it
// WatchIgnorePlugin = webpack.WatchIgnorePlugin
var WatchIgnorePlugin = require('./lib/watch-ignore');
var EnvifyPlugin = require('./lib/envify-plugin');

var DISABLE_MIN = !!envs('DISABLE_MIN');
var NODE_ENV = envs('ASSET_ENV', envs('NODE_ENV', 'production'));
var DEVELOPMENT = NODE_ENV === 'development';
var HASH = typeof envs('DISABLE_HASH') === 'undefined';
var MANIFEST = envs('MANIFEST');
var BUILD_TARGET = envs('BUILD_TARGET', 'web');
var EXTRACT_STYLE = !DEVELOPMENT && envs('EXTRACT_STYLE', BUILD_TARGET === 'node') !== '0';
var OUTPUT_PATTERN = envs('OUTPUT_PATTERN', '[name]');
var MAIN_ENTRY = DEVELOPMENT ? 'app' : envs('MAIN_ENTRY', 'main');
var COMPILE_ENV = envs('COMPILE_ENV', '1') !== '0';

module.exports = function(dirname, webpack) {
  var config = {
    sourcedir: dirname,
    cache: true
  };

  /**
   * Configure the entries
   */

  var target = config.target = BUILD_TARGET;

  // Autoload all of the modules
  config.entry = {};
  config.entry[MAIN_ENTRY] = dirname + '/index.js';

  if (target === 'node') {
    config.entry = {
      server: dirname + '/server'
    };
  }

  config.output = {
    path: Path.dirname(dirname) + '/build',
    filename: (DEVELOPMENT || !HASH) ?
      OUTPUT_PATTERN + '.js' :
      OUTPUT_PATTERN + (DISABLE_MIN ? '' : '.min') + '.js?[chunkhash]',
    libraryTarget: 'this'
  };

  if (DEVELOPMENT) {
    config.output.pathinfo = true;
    config.output.publicPath = '/build/';

    if (config.target === 'web') {
      // use the eval builder for optimal speed in development
      config.devtool = 'eval';

      var entries = [config.entry.app];
      // append the hot reload entries
      entries.push('webpack-dev-server/client?' + envs('WEBPACK_DEV_SERVER_URL', 'http://localhost:' + envs('PORT', '3000')));
      entries.push('webpack/hot/dev-server');
      config.entry = entries;
    }
  }

  /**
   * Configure plugins
   */

  config.plugins = [
    new webpack.IgnorePlugin(/vertx/),
    new webpack.DefinePlugin({
      'browser.env': '__env__',
      'process.BUILD_TARGET': JSON.stringify(target)
    }),
    new webpack.ResolverPlugin([
      new ResolveSelf()
    ], ['normal'])
  ];

  if (COMPILE_ENV) config.plugins.push(new EnvifyPlugin(null, webpack));

  var extractPlugin = new ExtractTextPlugin('style', OUTPUT_PATTERN + (DISABLE_MIN ? '' : '.min') + '.css?[chunkhash]');

  if (EXTRACT_STYLE) {
    config.plugins.push(extractPlugin);
  }

  if (!DEVELOPMENT) {
    config.plugins.push(
      new webpack.optimize.AggressiveMergingPlugin({
        moveToParents: true
      })
    );

    if (!envs('DISABLE_MIN')) config.plugins.push(
      new webpack.optimize.OccurrenceOrderPlugin(),
      new webpack.optimize.UglifyJsPlugin({
        output: {comments: false},
        sourceMap: false,
        exclude: [/\.ess/]
      }),
      new OptimizeCssPlugin({
        assetNameRegExp: /\.css/
      })
    );

    if (MANIFEST) config.plugins.push(createManifest(MANIFEST));
  } else {
    // append the hot reload plugin
    if (config.target === 'web') config.plugins.push(new webpack.HotModuleReplacementPlugin());
  }

  /**
   * configure resolution
   */

  config.resolve = {
    extensions: ['', '.js'],
    modulesDirectories: ['web_modules', 'node_modules', 'src/modules'],
    fallback: [process.cwd(), process.cwd() + '/node_modules'],
    alias: {
      fs: __dirname + '/lib/fs.shim.js'
    }
  };

  config.resolveLoader = {
    fallback: [process.cwd(), process.cwd() + '/node_modules']
  };

  /**
   * Configure loaders
   */

  config.module = {
    loaders: []
  };

  config.addLoader = function(ext, loader) {
    if (ext instanceof RegExp) return config.module.loaders.push({test: ext, loader: loader, loaders: loader});
    var obj = {};
    obj[ext] = loader;
    return config.module.loaders.push.apply(config.module.loaders, byExtension(obj));
  };

  /**
   * Setup style loading
   */

  config.addStyle = function(ext, loader, styleLoader) {
    styleLoader = styleLoader || 'style-loader';
    config.addLoader(ext, !EXTRACT_STYLE ? styleLoader + '!' + loader : extractPlugin.extract(loader));
  };

  /**
   * Configure development stuff
   */

  var watchIgnores = [];

  config.load = function() {
    if (watchIgnores.length) config.plugins.push(new WatchIgnorePlugin(watchIgnores));
    return webpack(config);
  };

  config.watchIgnore = function() {
    watchIgnores.push.apply(watchIgnores, arguments);
  };

  config.prefetchWildcard = function(pattern, opts, ignore) {
    glob.sync(pattern, opts).forEach(function(file) {
      if (ignore && file.match(ignore)) return;
      config.prefetchModule(resolve(process.cwd(), file));
    });
  };

  config.prefetchModule = function(context, request) {
    config.plugins.push(new webpack.PrefetchPlugin(context, request));
  };

  return config;
};

function createManifest(manifest) {
  return function() {
    this.plugin('done', function(stats) {
      var json = stats.toJson();
      var byChunkName = json.assetsByChunkName;
      var out = json.assets.reduce(function(acc, asset) {
        if (!asset.chunkNames.length) {
          if (~asset.name.indexOf('.js')) acc.chunks.push(asset.name);
          return acc;
        }
        if (~asset.name.indexOf('.css')) acc.styles.push(asset.name);
        if (~asset.name.indexOf('.js')) acc.scripts.push(asset.name);
        return acc;
      }, {
        styles: [],
        scripts: [],
        chunks: []
      });

      write(manifest, JSON.stringify(out, null, '  '));
    });
  };
}
<commit_msg>add autoprefixer setting<commit_after>/**
 * Module dependencies
 */

var envs = require('envs');
var write = require('fs').writeFileSync;
var list = require('fs').readdirSync;
var join = require('path').join;
var resolve = require('path').resolve;
var Path = require('path');
var glob = require('glob');
var byExtension = require('./lib/loaders-by-extension');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
// polyfill Promises
require('es6-promise').polyfill();
var OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin');
var ResolveSelf = require('./lib/resolve-self');
// remove this once webpack releases it
// WatchIgnorePlugin = webpack.WatchIgnorePlugin
var WatchIgnorePlugin = require('./lib/watch-ignore');
var EnvifyPlugin = require('./lib/envify-plugin');

var DISABLE_MIN = !!envs('DISABLE_MIN');
var NODE_ENV = envs('ASSET_ENV', envs('NODE_ENV', 'production'));
var DEVELOPMENT = NODE_ENV === 'development';
var HASH = typeof envs('DISABLE_HASH') === 'undefined';
var MANIFEST = envs('MANIFEST');
var BUILD_TARGET = envs('BUILD_TARGET', 'web');
var EXTRACT_STYLE = !DEVELOPMENT && envs('EXTRACT_STYLE', BUILD_TARGET === 'node') !== '0';
var OUTPUT_PATTERN = envs('OUTPUT_PATTERN', '[name]');
var MAIN_ENTRY = DEVELOPMENT ? 'app' : envs('MAIN_ENTRY', 'main');
var COMPILE_ENV = envs('COMPILE_ENV', '1') !== '0';

module.exports = function(dirname, webpack) {
  var config = {
    sourcedir: dirname,
    cache: true
  };

  /**
   * Configure the entries
   */

  var target = config.target = BUILD_TARGET;

  // Autoload all of the modules
  config.entry = {};
  config.entry[MAIN_ENTRY] = dirname + '/index.js';

  if (target === 'node') {
    config.entry = {
      server: dirname + '/server'
    };
  }

  config.output = {
    path: Path.dirname(dirname) + '/build',
    filename: (DEVELOPMENT || !HASH) ?
      OUTPUT_PATTERN + '.js' :
      OUTPUT_PATTERN + (DISABLE_MIN ? '' : '.min') + '.js?[chunkhash]',
    libraryTarget: 'this'
  };

  if (DEVELOPMENT) {
    config.output.pathinfo = true;
    config.output.publicPath = '/build/';

    if (config.target === 'web') {
      // use the eval builder for optimal speed in development
      config.devtool = 'eval';

      var entries = [config.entry.app];
      // append the hot reload entries
      entries.push('webpack-dev-server/client?' + envs('WEBPACK_DEV_SERVER_URL', 'http://localhost:' + envs('PORT', '3000')));
      entries.push('webpack/hot/dev-server');
      config.entry = entries;
    }
  }

  /**
   * Configure plugins
   */

  config.plugins = [
    new webpack.IgnorePlugin(/vertx/),
    new webpack.DefinePlugin({
      'browser.env': '__env__',
      'process.BUILD_TARGET': JSON.stringify(target)
    }),
    new webpack.ResolverPlugin([
      new ResolveSelf()
    ], ['normal'])
  ];

  if (COMPILE_ENV) config.plugins.push(new EnvifyPlugin(null, webpack));

  var extractPlugin = new ExtractTextPlugin('style', OUTPUT_PATTERN + (DISABLE_MIN ? '' : '.min') + '.css?[chunkhash]');

  if (EXTRACT_STYLE) {
    config.plugins.push(extractPlugin);
  }

  if (!DEVELOPMENT) {
    config.plugins.push(
      new webpack.optimize.AggressiveMergingPlugin({
        moveToParents: true
      })
    );

    if (!envs('DISABLE_MIN')) config.plugins.push(
      new webpack.optimize.OccurrenceOrderPlugin(),
      new webpack.optimize.UglifyJsPlugin({
        output: {comments: false},
        sourceMap: false,
        exclude: [/\.ess/]
      }),
      new OptimizeCssPlugin({
        assetNameRegExp: /\.css/,
        cssProcessorOptions: {
          autoprefixer: {add: true},
          discardComments: {removeAll: true}
        }
      })
    );

    if (MANIFEST) config.plugins.push(createManifest(MANIFEST));
  } else {
    // append the hot reload plugin
    if (config.target === 'web') config.plugins.push(new webpack.HotModuleReplacementPlugin());
  }

  /**
   * configure resolution
   */

  config.resolve = {
    extensions: ['', '.js'],
    modulesDirectories: ['web_modules', 'node_modules', 'src/modules'],
    fallback: [process.cwd(), process.cwd() + '/node_modules'],
    alias: {
      fs: __dirname + '/lib/fs.shim.js'
    }
  };

  config.resolveLoader = {
    fallback: [process.cwd(), process.cwd() + '/node_modules']
  };

  /**
   * Configure loaders
   */

  config.module = {
    loaders: []
  };

  config.addLoader = function(ext, loader) {
    if (ext instanceof RegExp) return config.module.loaders.push({test: ext, loader: loader, loaders: loader});
    var obj = {};
    obj[ext] = loader;
    return config.module.loaders.push.apply(config.module.loaders, byExtension(obj));
  };

  /**
   * Setup style loading
   */

  config.addStyle = function(ext, loader, styleLoader) {
    styleLoader = styleLoader || 'style-loader';
    config.addLoader(ext, !EXTRACT_STYLE ? styleLoader + '!' + loader : extractPlugin.extract(loader));
  };

  /**
   * Configure development stuff
   */

  var watchIgnores = [];

  config.load = function() {
    if (watchIgnores.length) config.plugins.push(new WatchIgnorePlugin(watchIgnores));
    return webpack(config);
  };

  config.watchIgnore = function() {
    watchIgnores.push.apply(watchIgnores, arguments);
  };

  config.prefetchWildcard = function(pattern, opts, ignore) {
    glob.sync(pattern, opts).forEach(function(file) {
      if (ignore && file.match(ignore)) return;
      config.prefetchModule(resolve(process.cwd(), file));
    });
  };

  config.prefetchModule = function(context, request) {
    config.plugins.push(new webpack.PrefetchPlugin(context, request));
  };

  return config;
};

function createManifest(manifest) {
  return function() {
    this.plugin('done', function(stats) {
      var json = stats.toJson();
      var byChunkName = json.assetsByChunkName;
      var out = json.assets.reduce(function(acc, asset) {
        if (!asset.chunkNames.length) {
          if (~asset.name.indexOf('.js')) acc.chunks.push(asset.name);
          return acc;
        }
        if (~asset.name.indexOf('.css')) acc.styles.push(asset.name);
        if (~asset.name.indexOf('.js')) acc.scripts.push(asset.name);
        return acc;
      }, {
        styles: [],
        scripts: [],
        chunks: []
      });

      write(manifest, JSON.stringify(out, null, '  '));
    });
  };
}
<|endoftext|>


<commit_before>const postcss = require('postcss');
const getLineHeight = require('./lib/getLineHeight');
const lhToRem = require('./lib/lhToRem');

const defaults = {
  rootSelector: ':root',
  rhythmUnit: 'lh'
};

module.exports = postcss.plugin('postcss-vertical-rhythm', (opts = defaults) => {
  let lineHeight;

  return (css) => {
    css.walkDecls((decl) => {

      // Check if the declaration is not inside the a print media query
      // if is the root selector and if is a font property
      if (decl.parent.parent.params !== 'print' &&
          decl.parent.selector === opts.rootSelector &&
          (decl.prop === 'font' || decl.prop === 'line-height')) {

        // Get the line-height
        lineHeight = getLineHeight(decl);
      }

      // Check if this declaration has a rhythm unit on it
      if (~ decl.value.indexOf(opts.rhythmUnit)) {

        // RegExp to capture declaration value
        const regexp = new RegExp('\\d*\\.?\\d+' + opts.rhythmUnit, 'gi');

        // Replace the current declaration value with the transformed value
        decl.value = decl.value.replace(regexp, (val) => lhToRem(parseFloat(val), lineHeight));
      }
    });
  };
});
<commit_msg>Update plugin name<commit_after>const postcss = require('postcss');
const getLineHeight = require('./lib/getLineHeight');
const lhToRem = require('./lib/lhToRem');

const defaults = {
  rootSelector: ':root',
  rhythmUnit: 'lh'
};

module.exports = postcss.plugin('postcss-lh', (opts = defaults) => {
  let lineHeight;

  return (css) => {
    css.walkDecls((decl) => {

      // Check if the declaration is not inside the a print media query
      // if is the root selector and if is a font property
      if (decl.parent.parent.params !== 'print' &&
          decl.parent.selector === opts.rootSelector &&
          (decl.prop === 'font' || decl.prop === 'line-height')) {

        // Get the line-height
        lineHeight = getLineHeight(decl);
      }

      // Check if this declaration has a rhythm unit on it
      if (~ decl.value.indexOf(opts.rhythmUnit)) {

        // RegExp to capture declaration value
        const regexp = new RegExp('\\d*\\.?\\d+' + opts.rhythmUnit, 'gi');

        // Replace the current declaration value with the transformed value
        decl.value = decl.value.replace(regexp, (val) => lhToRem(parseFloat(val), lineHeight));
      }
    });
  };
});
<|endoftext|>


<commit_before>'use strict'

var npmPath = require('npm-path')
var childProcess = require('child_process')
var spawn = require('cross-spawn')

var exec = childProcess.exec

// polyfill for childProcess.execSync
var execSync = childProcess.execSync

npmExec.spawn = npmSpawn
npmExec.spawnSync = npmSpawnSync
npmExec.sync = npmExecSync

npmExec.exec = npmExec
npmExec.execSync = npmExecSync

module.exports = npmExec

function npmExec (command, options, fn) {
  var a = normalizeExecArgs(command, options, fn)
  command = a[0]
  options = a[1]
  fn = a[2]
  return exec(command, augmentOptionsSync(options), fn)
}

function npmSpawn (command, args, options, fn) {
  var a = normalizeSpawnArgs(command, args, options, fn)
  command = a[0]
  args = a[1]
  options = a[2]
  fn = a[3]
  return spawn(command, args, augmentOptionsSync(options), fn)
}

function npmSpawnSync (command, args, options) {
  var a = normalizeSpawnArgs(command, args, options)
  command = a[0]
  args = a[1]
  options = a[2]
  return spawn.sync(command, args, augmentOptionsSync(options))
}

function npmExecSync (command, options) {
  var a = normalizeExecArgs(command, options)
  command = a[0]
  options = a[1]
  return execSync(command, augmentOptionsSync(options))
}

function augmentOptionsSync (options) {
  options = options || {}
  var newPath = npmPath.getSync(options)
  var env = Object.create(options.env)
  env[npmPath.PATH] = newPath
  options.env = env
  return options
}

function normalizeSpawnArgs (file /*, args, options */) {
  var args, options

  if (Array.isArray(arguments[1])) {
    args = arguments[1].slice(0)
    options = arguments[2]
  } else if (arguments[1] !== undefined &&
             (arguments[1] === null || typeof arguments[1] !== 'object')) {
    throw new TypeError('Incorrect value of args option')
  } else {
    args = []
    options = arguments[1]
  }

  if (options === undefined) { options = {} } else if (options === null || typeof options !== 'object') { throw new TypeError('options argument must be an object') }

  return [file, args, options]
}

function normalizeExecArgs (command /*, options, callback */) {
  var options, callback

  if (typeof arguments[1] === 'function') {
    options = undefined
    callback = arguments[1]
  } else {
    options = arguments[1]
    callback = arguments[2]
  }
  callback = callback || noopErr
  return [command, options, callback]
}

function noopErr (err) {
  if (err) throw err
}
<commit_msg>Fix broken package reference.<commit_after>'use strict'

var npmPath = require('npm-path')
var childProcess = require('child_process')

var exec = childProcess.exec
var spawn = childProcess.spawn
var spawnSync = childProcess.spawnSync

// polyfill for childProcess.execSync
var execSync = childProcess.execSync

npmExec.spawn = npmSpawn
npmExec.spawnSync = npmSpawnSync
npmExec.sync = npmExecSync

npmExec.exec = npmExec
npmExec.execSync = npmExecSync

module.exports = npmExec

function npmExec (command, options, fn) {
  var a = normalizeExecArgs(command, options, fn)
  command = a[0]
  options = a[1]
  fn = a[2]
  return exec(command, augmentOptionsSync(options), fn)
}

function npmSpawn (command, args, options, fn) {
  var a = normalizeSpawnArgs(command, args, options, fn)
  command = a[0]
  args = a[1]
  options = a[2]
  fn = a[3]
  return spawn(command, args, augmentOptionsSync(options), fn)
}

function npmSpawnSync (command, args, options) {
  var a = normalizeSpawnArgs(command, args, options)
  command = a[0]
  args = a[1]
  options = a[2]
  return spawnSync(command, args, augmentOptionsSync(options))
}

function npmExecSync (command, options) {
  var a = normalizeExecArgs(command, options)
  command = a[0]
  options = a[1]
  return execSync(command, augmentOptionsSync(options))
}

function augmentOptionsSync (options) {
  options = options || {}
  var newPath = npmPath.getSync(options)
  var env = Object.create(options.env)
  env[npmPath.PATH] = newPath
  options.env = env
  return options
}

function normalizeSpawnArgs (file /*, args, options */) {
  var args, options

  if (Array.isArray(arguments[1])) {
    args = arguments[1].slice(0)
    options = arguments[2]
  } else if (arguments[1] !== undefined &&
             (arguments[1] === null || typeof arguments[1] !== 'object')) {
    throw new TypeError('Incorrect value of args option')
  } else {
    args = []
    options = arguments[1]
  }

  if (options === undefined) { options = {} } else if (options === null || typeof options !== 'object') { throw new TypeError('options argument must be an object') }

  return [file, args, options]
}

function normalizeExecArgs (command /*, options, callback */) {
  var options, callback

  if (typeof arguments[1] === 'function') {
    options = undefined
    callback = arguments[1]
  } else {
    options = arguments[1]
    callback = arguments[2]
  }
  callback = callback || noopErr
  return [command, options, callback]
}

function noopErr (err) {
  if (err) throw err
}
<|endoftext|>


<commit_before>var _ = require('underscore');

var parser = module.exports = {};

parser.parseName = function (name) {
    var salutations = ['mr', 'master', 'mister', 'mrs', 'miss', 'ms', 'dr', 'prof', 'rev', 'fr', 'judge', 'honorable', 'hon']
        , suffixes = ['i', 'ii', 'iii', 'iv', 'v', 'senior', 'junior', 'jr', 'sr', 'phd', 'apr', 'rph', 'pe', 'md', 'ma', 'dmd', 'cme']
        , compound = ['vere', 'von', 'van', 'de', 'del', 'della', 'der', 'di', 'da', 'pietro', 'vanden', 'du', 'st.', 'st', 'la', 'lo', 'ter', 'bin', 'ibn', 'te', 'ten', 'op'];

    var parts = name.trim().split(/\s+/)
        , attrs = {};

    if (!parts.length) {
        return attrs;
    }

    if ( parts.length === 1 ) {
        attrs.firstName = parts[0];
    }

    //handle suffix first always, remove trailing comma if there is one
    if ( parts.length > 1 && _.indexOf(suffixes, _.last(parts).toLowerCase().replace(/\./g, '')) > -1) {
        attrs.suffix = parts.pop();
        parts[parts.length-1] = _.last(parts).replace(',', '');
    }

    //look for a comma to know we have last name first format
    var firstNameFirstFormat = _.every(parts, function(part) {
        return part.indexOf(',') === -1;
    })

    if (!firstNameFirstFormat) {
    //last name first format
    //assuming salutations are never used in this format

        //tracker variable for where first name begins in parts array
        var firstNameIndex;

        //location of first comma will separate last name from rest
        //join all parts leading to first comma as last name

        var lastName = _.reduce(parts, function (lastName, current, index) {
            if (!Array.isArray(lastName)) {
                return lastName;
            }
            if (current.indexOf(',') === -1) {
                lastName.push(current)
                return lastName;
            } else {
                current = current.replace(',', '');
                lastName.push(current);
                firstNameIndex = index + 1;
                return lastName.join(' ');
            }
        }, []);

        attrs.lastName = lastName;

        var remainingParts = parts.slice(firstNameIndex);
        if (remainingParts.length > 1) {
            attrs.firstName = remainingParts.shift();
            attrs.middleName = remainingParts.join(' ');
        } else if (remainingParts.length) {
            attrs.firstName = remainingParts[0];
        }

        //create full name from attrs object
        var nameWords = [];
        if (attrs.firstName) {
            nameWords.push(attrs.firstName);
        }
        if (attrs.middleName) {
            nameWords.push(attrs.middleName)
        }
        nameWords.push(attrs.lastName)
        if (attrs.suffix) {
            nameWords.push(attrs.suffix);
        }
        attrs.fullName = nameWords.join(' ');

    } else {
    //first name first format

        if ( parts.length > 1 && _.indexOf(salutations, _.first(parts).toLowerCase().replace(/\./g, '')) > -1) {
            attrs.salutation = parts.shift();
            attrs.firstName = parts.shift();
        } else {
            attrs.firstName = parts.shift();
        }

        attrs.lastName = parts.pop();

        // test for compound last name, we reverse because middle name is last bit to be defined.
        // We already know lastname, so check next word if its part of a compound last name.
        var revParts = parts.reverse()
            , compoundParts = [];

        _.every(revParts, function(part, i, all){
            var test = part.toLowerCase().replace(/\./g, '');

            if (_.indexOf(compound, test) > -1 ) {
                compoundParts.push(part);

                return true;
            }

            //break on first non compound word
            return false;
        });

        //join compound parts with known last name
        if ( compoundParts.length ) {
            attrs.lastName = compoundParts.reverse().join(' ') + ' ' + attrs.lastName;

            parts = _.difference(parts, compoundParts);
        }

        if ( parts.length ) {
            attrs.middleName = parts.join(' ');
        }

        //remove comma like "<lastName>, Jr."
        if ( attrs.lastName ) {
            attrs.lastName = attrs.lastName.replace(',', '');
        }

        //save a copy of original
        attrs.fullName = name;

    }
    //console.log('attrs:', JSON.stringify(attrs));
    return attrs;
};

parser.getFullestName = function(str){
    var name = str
        , names = [];

    //find fullname from strings like 'Jon and Sue Doyle'
    if ( name.indexOf('&') > -1 || name.toLowerCase().indexOf(' and ') > -1 ) {
        names = name.split(/\s+(?:and|&)\s+/gi);

        //pluck the name with the most parts (first, middle, last) from the array.
        //will grab 'Sue Doyle' in 'Jon & Sue Anne Doyle'
        if ( names.length ) {
            name = names.sort(function(a, b) {
                return b.split(/\s+/).length - a.split(/\s+/).length;
            })[0];
        }
    }

    return name;
};

parser.parseAddress = function(str){
    //416 W. Manchester Blvd., Inglewood, CA  90301
    var parts = str.split(/,\s+/).reverse()
        , stateZip
        , city
        , address = {};

    stateZip = parts[0].split(/\s+/);
    parts.shift();

    city = parts.shift();

    address.address = parts.reverse().join(', ');
    address.city = city;
    address.state = stateZip[0];
    address.zip = stateZip[1];
    address.fullAddress = str;

    return address;
};
<commit_msg>(bug) last name first fix #7<commit_after>var _ = require('underscore');

var parser = module.exports = {};

parser.parseName = function (name) {
    var salutations = ['mr', 'master', 'mister', 'mrs', 'miss', 'ms', 'dr', 'prof', 'rev', 'fr', 'judge', 'honorable', 'hon']
        , suffixes = ['i', 'ii', 'iii', 'iv', 'v', 'senior', 'junior', 'jr', 'sr', 'phd', 'apr', 'rph', 'pe', 'md', 'ma', 'dmd', 'cme']
        , compound = ['vere', 'von', 'van', 'de', 'del', 'della', 'der', 'di', 'da', 'pietro', 'vanden', 'du', 'st.', 'st', 'la', 'lo', 'ter', 'bin', 'ibn', 'te', 'ten', 'op'];

    var parts = name.trim().split(/\s+/)
        , attrs = {};

    if (!parts.length) {
        return attrs;
    }

    if ( parts.length === 1 ) {
        attrs.firstName = parts[0];
    }

    //handle suffix first always, remove trailing comma if there is one
    if ( parts.length > 1 && _.indexOf(suffixes, _.last(parts).toLowerCase().replace(/\./g, '')) > -1) {
        attrs.suffix = parts.pop();
        parts[parts.length-1] = _.last(parts).replace(',', '');
    }

    //look for a comma to know we have last name first format
    var firstNameFirstFormat = _.every(parts, function(part) {
        return part.indexOf(',') === -1;
    })

    if (!firstNameFirstFormat) {
    //last name first format
    //assuming salutations are never used in this format

        //tracker variable for where first name begins in parts array
        var firstNameIndex;

        //location of first comma will separate last name from rest
        //join all parts leading to first comma as last name
        var lastName = _.reduce(parts, function (lastName, current, index) {
            if (!Array.isArray(lastName)) {
                return lastName;
            }
            if (current.indexOf(',') === -1) {
                lastName.push(current)
                return lastName;
            } else {
                current = current.replace(',', '');
                lastName.push(current);
                firstNameIndex = index + 1;
                return lastName.join(' ');
            }
        }, []);

        attrs.lastName = lastName;

        var remainingParts = parts.slice(firstNameIndex);
        if (remainingParts.length > 1) {
            attrs.firstName = remainingParts.shift();
            attrs.middleName = remainingParts.join(' ');
        } else if (remainingParts.length) {
            attrs.firstName = remainingParts[0];
        }

        //create full name from attrs object
        var nameWords = [];
        if (attrs.firstName) {
            nameWords.push(attrs.firstName);
        }
        if (attrs.middleName) {
            nameWords.push(attrs.middleName)
        }
        nameWords.push(attrs.lastName)
        if (attrs.suffix) {
            nameWords.push(attrs.suffix);
        }
        attrs.fullName = nameWords.join(' ');

    } else {
    //first name first format

        if ( parts.length > 1 && _.indexOf(salutations, _.first(parts).toLowerCase().replace(/\./g, '')) > -1) {
            attrs.salutation = parts.shift();
            attrs.firstName = parts.shift();
        } else {
            attrs.firstName = parts.shift();
        }

        attrs.lastName = parts.pop();

        // test for compound last name, we reverse because middle name is last bit to be defined.
        // We already know lastname, so check next word if its part of a compound last name.
        var revParts = parts.reverse()
            , compoundParts = [];

        _.every(revParts, function(part, i, all){
            var test = part.toLowerCase().replace(/\./g, '');

            if (_.indexOf(compound, test) > -1 ) {
                compoundParts.push(part);

                return true;
            }

            //break on first non compound word
            return false;
        });

        //join compound parts with known last name
        if ( compoundParts.length ) {
            attrs.lastName = compoundParts.reverse().join(' ') + ' ' + attrs.lastName;

            parts = _.difference(parts, compoundParts);
        }

        if ( parts.length ) {
            attrs.middleName = parts.join(' ');
        }

        //remove comma like "<lastName>, Jr."
        if ( attrs.lastName ) {
            attrs.lastName = attrs.lastName.replace(',', '');
        }

        //save a copy of original
        attrs.fullName = name;

    }
    //console.log('attrs:', JSON.stringify(attrs));
    return attrs;
};

parser.getFullestName = function(str){
    var name = str
        , names = [];

    //find fullname from strings like 'Jon and Sue Doyle'
    if ( name.indexOf('&') > -1 || name.toLowerCase().indexOf(' and ') > -1 ) {
        names = name.split(/\s+(?:and|&)\s+/gi);

        //pluck the name with the most parts (first, middle, last) from the array.
        //will grab 'Sue Doyle' in 'Jon & Sue Anne Doyle'
        if ( names.length ) {
            name = names.sort(function(a, b) {
                return b.split(/\s+/).length - a.split(/\s+/).length;
            })[0];
        }
    }

    return name;
};

parser.parseAddress = function(str){
    //416 W. Manchester Blvd., Inglewood, CA  90301
    var parts = str.split(/,\s+/).reverse()
        , stateZip
        , city
        , address = {};

    stateZip = parts[0].split(/\s+/);
    parts.shift();

    city = parts.shift();

    address.address = parts.reverse().join(', ');
    address.city = city;
    address.state = stateZip[0];
    address.zip = stateZip[1];
    address.fullAddress = str;

    return address;
};
<|endoftext|>