<commit_before>//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

const async = require('async');
const utils = require('../utils');
const debug = require('debug')('azureossportal');

function OpenSourceUser(ossInstance, githubId, optionalGitHubInstance) {
  this.id = githubId;
  this.oss = ossInstance;
  this.otherFields = {};
  this.link = null;
  this._detailsLoaded = false;
  if (optionalGitHubInstance) {
    setDetails(this, optionalGitHubInstance);
  }
}

// ----------------------------------------------------------------------------
// Properties of interest in the standard GitHub response for a user
// ----------------------------------------------------------------------------
var detailsToCopy = [
  'login',
  'avatar_url',
  // only in detailed info responses:
  'name',
  'company',
  'location',
  'email',
  'bio',
  'created_at',
  'updated_at',
];
var detailsToSkip = [
  'id',
  'gravatar_id',
  'url',
  'html_url',
  'followers_url',
  'following_url',
  'gists_url',
  'starred_url',
  'subscriptions_url',
  'organizations_url',
  'repos_url',
  'events_url',
  'received_events_url',
  'hooks_url',
  'issues_url',
  'type',
  'site_admin',
  // only in detailed info responses:
  'blog',
  'hireable',
  'public_repos',
  'public_gists',
  'followers',
  'following',
  // organizations:
  'members_url',
  'public_members_url',
  'description',
  'total_private_repos',
  'owned_private_repos',
  'private_gists',
  'disk_usage',
  'collaborators',
  'billing_email',
  'plan',
  // when in the context of a collaborators response only:
  'permissions',
];

// ----------------------------------------------------------------------------
// Retrieve the link contact information, if the link has been loaded.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.contactEmail = function () {
  return this.link ? this.link.aadupn : null;
};

// ----------------------------------------------------------------------------
// Retrieve the link contact information alias subset, if link loaded.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.corporateAlias = function () {
  if (this.link && this.link.aadupn) {
    var email = this.link.aadupn;
    var i = email.indexOf('@');
    if (i >= 0) {
      return email.substring(0, i);
    }
  }
  return null;
};

// ----------------------------------------------------------------------------
// Retrieve the link contact information alias subset, if link loaded.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.corporateProfileUrl = function () {
  var alias = this.corporateAlias();
  var corporateSettings = this.oss.setting('corporate');
  if (alias && corporateSettings && corporateSettings.profile && corporateSettings.profile.prefix) {
    return corporateSettings.profile.prefix + alias;
  }
  return null;
};

// ----------------------------------------------------------------------------
// Retrieve the link contact information, if the link has been loaded.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.contactName = function () {
  if (this.link) {
    return this.link.aadname || this.login;
  }
  return this.login;
};

// ----------------------------------------------------------------------------
// Retrieves the URL for the user's avatar, if present. If the user's details
// have not been loaded, we will not yet have an avatar URL.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.avatar = function (optionalSize) {
  if (!optionalSize) {
    optionalSize = 80;
  }
  if (this.avatar_url) {
    return this.avatar_url + '&s=' + optionalSize;
  } else {
    return undefined;
  }
};

// ----------------------------------------------------------------------------
// Returns the user's active memberships in organizations managed by this
// portal.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.getActiveOrganizationMemberships = function (callback) {
  var self = this;
  var oss = self.oss;
  var currentOrganizationMemberships = [];
  async.each(oss.orgs(), function (o, cb) {
    o.queryUserMembership(false /* no caching */, function (error, result) {
      var state = null;
      if (result && result.state) {
        state = result.state;
      }
      if (state == 'active') // IGNORE: need to find out how to cancel pending invites... || state == 'pending') {
        currentOrganizationMemberships.push(o);
      cb(error);
    });
  }, function (error) {
    callback(error, currentOrganizationMemberships);
  });
};

// ----------------------------------------------------------------------------
// Unlinks the user. For now we assume that this is a very serious operation.
// This will unlink and then also drop from all GitHub organizations managed by
// this instance of the portal.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.unlinkAndDrop = function (callback) {
  const self = this;
  const oss = self.oss;
  const operations = oss.operations;
  let eventData = null;
  self.getLink((getLinkError, linkObject) => {
    // We ignore the get link error: should must be able to process drops without a link
    if (linkObject) {
      const aadIdentity = {
        preferredName: linkObject.aadname,
        userPrincipalName: linkObject.aadupn,
        id: linkObject.aadoid,
      };
      eventData = {
        github: {
          id: linkObject.ghid,
          login: linkObject.ghu,
        },
        aad: aadIdentity,
      };
    } else {
      // Fallback to providing any available GitHub information
      eventData = {
        github: {
          id: oss.id.github,
          login: oss.usernames.github,
        },
      };
    }
  });
  self.getActiveOrganizationMemberships((getOrganizationsError, currentOrganizationMemberships) => {
    if (getOrganizationsError) {
      return callback(getOrganizationsError);
    }
    async.each(currentOrganizationMemberships, function (org, cb) {
      org.removeUserMembership(function () {
        // TODO: We now continue with deletes when one fails. Common
        // failure case is when they have a pending invite, it will live
        // on... which is not ideal.
        cb();
      });
    }, function (/* ignored error per above statement */) {
      var dc = self.oss.dataClient();
      dc.removeLink(oss.id.github, (removalError) => {
        oss.insights.trackEvent('UserUnlink');
        oss.invalidateLinkCache(() => {
          if (operations) {
            operations.fireUnlinkEvent(eventData);
          }
          // TODO: Notify via link events that an unlink operation has happened
          callback(removalError);
        });
      });
    });
  });
};

// ----------------------------------------------------------------------------
// Updates a link for the user. At this time it does not do much validation.
// This is a full replacement of the link, not a merge.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.updateLink = function (updatedLink, callback) {
  var self = this;
  if (self.link && self.link.ghid !== self.id) {
    callback(new Error('Had trouble before merging entities for a link update.'));
  }
  var dc = self.oss.dataClient();
  dc.updateLink(self.id, updatedLink, (updateError) => {
    if (updateError) {
      return callback(utils.wrapError(updateError, `We were not able to perform an update to the link for user ID ${self.id} at this time.`));
    }
    self.oss.invalidateLinkCache('aad', updatedLink.aadoid || 'no-aad-oid', () => {
      self.link = updatedLink;
      return callback(null, self.link);
    });
  });
};

// ----------------------------------------------------------------------------
// Retrieve the link, if any, for this user from the underlying datastore. Will
// cache the value in memory for this instance, since the lifetime of these
// objects is a single request.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.getLink = function (callback) {
  if (this.link) {
    return callback(null, this.link);
  }
  var self = this;
  var dc = self.oss.dataClient();
  dc.getLink(self.id, function (error, link) {
    if (error) {
      return callback(utils.wrapError(error, 'We were not able to retrieve information about the link for user ' + self.id + ' at this time.'));
    }
    self.link = (link === false) ? false : dc.reduceEntity(link);
    callback(null, self.link);
  });
};

OpenSourceUser.prototype.getLinkRequired = function (callback) {
  var self = this;
  this.getLink(function (error) {
    if (!error && self.link === false) {
      error = new Error('No link retrieved.');
    }
    if (error) {
      return callback(error);
    }
    callback(null, self.link);
  });
};

// ----------------------------------------------------------------------------
// Special-use function to set the link when provided elsewhere. This is
// helpful since we can efficiently query a large set of links for team list
// scenarios and then set them here.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.setLinkInstance = function (links, optionalSuppressDebug) {
  if (!Array.isArray(links)) {
    links = [links];
  }
  for (var i = 0; i < links.length; i++) {
    var link = links[i];
    if (link.ghid === this.id) {
      this.link = link;
      break;
    }
  }
  if (!this.link && optionalSuppressDebug !== true) {
    throw new Error('No matching link was provided for the user ID ' + this.id + '.');
  }
};

// ----------------------------------------------------------------------------
// Special-use function to set the link when provided elsewhere. This is
// helpful since we can efficiently query a large set of links for team list
// scenarios and then set them here. Captures a throw and ignores the issue.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.trySetLinkInstance = function (links, optionalSuppressDebug) {
  try {
    this.setLinkInstance(links, optionalSuppressDebug);
  } catch (error) {
    debug('trySetLinkInstance: No link exists for user ' + this.id);
  }
};

// ----------------------------------------------------------------------------
// Load the GitHub details for the user.
// Problem: we have the ID, but GitHub cheaply prefers usernames, not IDs...
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.getDetailsByUsername = function (login, callback) {
  var self = this;
  var username = this.login;
  if (typeof login == 'function') {
    callback = login;
    login = null;
  } else {
    username = login;
  }
  if (!username) {
    return callback(new Error('No username provided for retrieving the details of user ' + self.id));
  }
  self.oss.createGenericGitHubClient().user(username).info(function (error, info) {
    if (error) {
      return callback(utils.wrapError(error, 'We were unable to retrieve information about user ' + username + ' (' + self.id + ').'));
    }
    var copy = {};
    Object.assign(copy, info);
    setDetails(self, info); // destructive operation
    callback(null, copy);
  });
};

OpenSourceUser.prototype.getProfileCreatedDate = function () {
  if (this.created_at) {
    return new Date(this.created_at);
  }
  return null;
};

OpenSourceUser.prototype.getProfileUpdatedDate = function () {
  if (this.updated_at) {
    return new Date(this.updated_at);
  }
  return null;
};

OpenSourceUser.prototype.debugView = function () {
  var obj = {};
  for (var key in this) {
    var val = this[key];
    if (typeof val == 'string') {
      obj[key] = val;
    } else {
      if (key == 'otherFields' || key == 'link' || key == 'bio') {
        obj[key] = val;
      }
    }
  }
  return obj;
};

function setDetails(self, details) {
  var key = null;
  for (var i = 0; i < detailsToCopy.length; i++) {
    key = detailsToCopy[i];
    self[key] = utils.stealValue(details, key);
  }
  for (i = 0; i < detailsToSkip.length; i++) {
    key = detailsToSkip[i];
    self.otherFields[key] = utils.stealValue(details, key);
  }
  for (var k in details) {
    debug('User details import, remaining key: ' + k);
  }
  self._detailsLoaded = true;
}

module.exports = OpenSourceUser;
<commit_msg>Removing old comment<commit_after>//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

const async = require('async');
const utils = require('../utils');
const debug = require('debug')('azureossportal');

function OpenSourceUser(ossInstance, githubId, optionalGitHubInstance) {
  this.id = githubId;
  this.oss = ossInstance;
  this.otherFields = {};
  this.link = null;
  this._detailsLoaded = false;
  if (optionalGitHubInstance) {
    setDetails(this, optionalGitHubInstance);
  }
}

// ----------------------------------------------------------------------------
// Properties of interest in the standard GitHub response for a user
// ----------------------------------------------------------------------------
var detailsToCopy = [
  'login',
  'avatar_url',
  // only in detailed info responses:
  'name',
  'company',
  'location',
  'email',
  'bio',
  'created_at',
  'updated_at',
];
var detailsToSkip = [
  'id',
  'gravatar_id',
  'url',
  'html_url',
  'followers_url',
  'following_url',
  'gists_url',
  'starred_url',
  'subscriptions_url',
  'organizations_url',
  'repos_url',
  'events_url',
  'received_events_url',
  'hooks_url',
  'issues_url',
  'type',
  'site_admin',
  // only in detailed info responses:
  'blog',
  'hireable',
  'public_repos',
  'public_gists',
  'followers',
  'following',
  // organizations:
  'members_url',
  'public_members_url',
  'description',
  'total_private_repos',
  'owned_private_repos',
  'private_gists',
  'disk_usage',
  'collaborators',
  'billing_email',
  'plan',
  // when in the context of a collaborators response only:
  'permissions',
];

// ----------------------------------------------------------------------------
// Retrieve the link contact information, if the link has been loaded.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.contactEmail = function () {
  return this.link ? this.link.aadupn : null;
};

// ----------------------------------------------------------------------------
// Retrieve the link contact information alias subset, if link loaded.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.corporateAlias = function () {
  if (this.link && this.link.aadupn) {
    var email = this.link.aadupn;
    var i = email.indexOf('@');
    if (i >= 0) {
      return email.substring(0, i);
    }
  }
  return null;
};

// ----------------------------------------------------------------------------
// Retrieve the link contact information alias subset, if link loaded.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.corporateProfileUrl = function () {
  var alias = this.corporateAlias();
  var corporateSettings = this.oss.setting('corporate');
  if (alias && corporateSettings && corporateSettings.profile && corporateSettings.profile.prefix) {
    return corporateSettings.profile.prefix + alias;
  }
  return null;
};

// ----------------------------------------------------------------------------
// Retrieve the link contact information, if the link has been loaded.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.contactName = function () {
  if (this.link) {
    return this.link.aadname || this.login;
  }
  return this.login;
};

// ----------------------------------------------------------------------------
// Retrieves the URL for the user's avatar, if present. If the user's details
// have not been loaded, we will not yet have an avatar URL.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.avatar = function (optionalSize) {
  if (!optionalSize) {
    optionalSize = 80;
  }
  if (this.avatar_url) {
    return this.avatar_url + '&s=' + optionalSize;
  } else {
    return undefined;
  }
};

// ----------------------------------------------------------------------------
// Returns the user's active memberships in organizations managed by this
// portal.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.getActiveOrganizationMemberships = function (callback) {
  var self = this;
  var oss = self.oss;
  var currentOrganizationMemberships = [];
  async.each(oss.orgs(), function (o, cb) {
    o.queryUserMembership(false /* no caching */, function (error, result) {
      var state = null;
      if (result && result.state) {
        state = result.state;
      }
      if (state == 'active') // IGNORE: need to find out how to cancel pending invites... || state == 'pending') {
        currentOrganizationMemberships.push(o);
      cb(error);
    });
  }, function (error) {
    callback(error, currentOrganizationMemberships);
  });
};

// ----------------------------------------------------------------------------
// Unlinks the user. For now we assume that this is a very serious operation.
// This will unlink and then also drop from all GitHub organizations managed by
// this instance of the portal.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.unlinkAndDrop = function (callback) {
  const self = this;
  const oss = self.oss;
  const operations = oss.operations;
  let eventData = null;
  self.getLink((getLinkError, linkObject) => {
    // We ignore the get link error: should must be able to process drops without a link
    if (linkObject) {
      const aadIdentity = {
        preferredName: linkObject.aadname,
        userPrincipalName: linkObject.aadupn,
        id: linkObject.aadoid,
      };
      eventData = {
        github: {
          id: linkObject.ghid,
          login: linkObject.ghu,
        },
        aad: aadIdentity,
      };
    } else {
      // Fallback to providing any available GitHub information
      eventData = {
        github: {
          id: oss.id.github,
          login: oss.usernames.github,
        },
      };
    }
  });
  self.getActiveOrganizationMemberships((getOrganizationsError, currentOrganizationMemberships) => {
    if (getOrganizationsError) {
      return callback(getOrganizationsError);
    }
    async.each(currentOrganizationMemberships, function (org, cb) {
      org.removeUserMembership(function () {
        // TODO: We now continue with deletes when one fails. Common
        // failure case is when they have a pending invite, it will live
        // on... which is not ideal.
        cb();
      });
    }, function (/* ignored error per above statement */) {
      var dc = self.oss.dataClient();
      dc.removeLink(oss.id.github, (removalError) => {
        oss.insights.trackEvent('UserUnlink');
        oss.invalidateLinkCache(() => {
          if (operations) {
            operations.fireUnlinkEvent(eventData);
          }
          callback(removalError);
        });
      });
    });
  });
};

// ----------------------------------------------------------------------------
// Updates a link for the user. At this time it does not do much validation.
// This is a full replacement of the link, not a merge.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.updateLink = function (updatedLink, callback) {
  var self = this;
  if (self.link && self.link.ghid !== self.id) {
    callback(new Error('Had trouble before merging entities for a link update.'));
  }
  var dc = self.oss.dataClient();
  dc.updateLink(self.id, updatedLink, (updateError) => {
    if (updateError) {
      return callback(utils.wrapError(updateError, `We were not able to perform an update to the link for user ID ${self.id} at this time.`));
    }
    self.oss.invalidateLinkCache('aad', updatedLink.aadoid || 'no-aad-oid', () => {
      self.link = updatedLink;
      return callback(null, self.link);
    });
  });
};

// ----------------------------------------------------------------------------
// Retrieve the link, if any, for this user from the underlying datastore. Will
// cache the value in memory for this instance, since the lifetime of these
// objects is a single request.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.getLink = function (callback) {
  if (this.link) {
    return callback(null, this.link);
  }
  var self = this;
  var dc = self.oss.dataClient();
  dc.getLink(self.id, function (error, link) {
    if (error) {
      return callback(utils.wrapError(error, 'We were not able to retrieve information about the link for user ' + self.id + ' at this time.'));
    }
    self.link = (link === false) ? false : dc.reduceEntity(link);
    callback(null, self.link);
  });
};

OpenSourceUser.prototype.getLinkRequired = function (callback) {
  var self = this;
  this.getLink(function (error) {
    if (!error && self.link === false) {
      error = new Error('No link retrieved.');
    }
    if (error) {
      return callback(error);
    }
    callback(null, self.link);
  });
};

// ----------------------------------------------------------------------------
// Special-use function to set the link when provided elsewhere. This is
// helpful since we can efficiently query a large set of links for team list
// scenarios and then set them here.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.setLinkInstance = function (links, optionalSuppressDebug) {
  if (!Array.isArray(links)) {
    links = [links];
  }
  for (var i = 0; i < links.length; i++) {
    var link = links[i];
    if (link.ghid === this.id) {
      this.link = link;
      break;
    }
  }
  if (!this.link && optionalSuppressDebug !== true) {
    throw new Error('No matching link was provided for the user ID ' + this.id + '.');
  }
};

// ----------------------------------------------------------------------------
// Special-use function to set the link when provided elsewhere. This is
// helpful since we can efficiently query a large set of links for team list
// scenarios and then set them here. Captures a throw and ignores the issue.
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.trySetLinkInstance = function (links, optionalSuppressDebug) {
  try {
    this.setLinkInstance(links, optionalSuppressDebug);
  } catch (error) {
    debug('trySetLinkInstance: No link exists for user ' + this.id);
  }
};

// ----------------------------------------------------------------------------
// Load the GitHub details for the user.
// Problem: we have the ID, but GitHub cheaply prefers usernames, not IDs...
// ----------------------------------------------------------------------------
OpenSourceUser.prototype.getDetailsByUsername = function (login, callback) {
  var self = this;
  var username = this.login;
  if (typeof login == 'function') {
    callback = login;
    login = null;
  } else {
    username = login;
  }
  if (!username) {
    return callback(new Error('No username provided for retrieving the details of user ' + self.id));
  }
  self.oss.createGenericGitHubClient().user(username).info(function (error, info) {
    if (error) {
      return callback(utils.wrapError(error, 'We were unable to retrieve information about user ' + username + ' (' + self.id + ').'));
    }
    var copy = {};
    Object.assign(copy, info);
    setDetails(self, info); // destructive operation
    callback(null, copy);
  });
};

OpenSourceUser.prototype.getProfileCreatedDate = function () {
  if (this.created_at) {
    return new Date(this.created_at);
  }
  return null;
};

OpenSourceUser.prototype.getProfileUpdatedDate = function () {
  if (this.updated_at) {
    return new Date(this.updated_at);
  }
  return null;
};

OpenSourceUser.prototype.debugView = function () {
  var obj = {};
  for (var key in this) {
    var val = this[key];
    if (typeof val == 'string') {
      obj[key] = val;
    } else {
      if (key == 'otherFields' || key == 'link' || key == 'bio') {
        obj[key] = val;
      }
    }
  }
  return obj;
};

function setDetails(self, details) {
  var key = null;
  for (var i = 0; i < detailsToCopy.length; i++) {
    key = detailsToCopy[i];
    self[key] = utils.stealValue(details, key);
  }
  for (i = 0; i < detailsToSkip.length; i++) {
    key = detailsToSkip[i];
    self.otherFields[key] = utils.stealValue(details, key);
  }
  for (var k in details) {
    debug('User details import, remaining key: ' + k);
  }
  self._detailsLoaded = true;
}

module.exports = OpenSourceUser;
<|endoftext|>


<commit_before>import d3 from 'd3';
import './country_map.css';
import { colorScalerFactory } from '../javascripts/modules/colors';
import d3ScaleChromatic from 'd3-scale-chromatic';

function countryMapChart(slice, payload) {
  // CONSTANTS
  const fd = payload.form_data;
  let path;
  let g;
  let bigText;
  let resultText;
  const container = slice.container;
  const data = payload.data;

  const colorScaler = colorScalerFactory(fd.linear_color_scheme, data, v => v.metric, 'map');
  const colorMap = {};
  data.forEach((d) => {
    colorMap[d.country_id] = colorScaler(d.metric);
  });
  const colorFn = d => colorMap[d.properties.ISO] || 'none';

  let centered;
  path = d3.geo.path();
  d3.select(slice.selector).selectAll('*').remove();
  const div = d3.select(slice.selector)
    .append('svg:svg')
    .attr('width', slice.width())
    .attr('height', slice.height())
    .attr('preserveAspectRatio', 'xMidYMid meet');

  container.css('height', slice.height());
  container.css('width', slice.width());

  const clicked = function (d) {
    let x;
    let y;
    let k;
    let bigTextX;
    let bigTextY;
    let bigTextSize;
    let resultTextX;
    let resultTextY;

    if (d && centered !== d) {
      const centroid = path.centroid(d);
      x = centroid[0];
      y = centroid[1];
      bigTextX = centroid[0];
      bigTextY = centroid[1] - 40;
      resultTextX = centroid[0];
      resultTextY = centroid[1] - 40;
      bigTextSize = '6px';
      k = 4;
      centered = d;
    } else {
      x = slice.width() / 2;
      y = slice.height() / 2;
      bigTextX = 0;
      bigTextY = 0;
      resultTextX = 0;
      resultTextY = 0;
      bigTextSize = '30px';
      k = 1;
      centered = null;
    }

    g.transition()
      .duration(750)
      .attr('transform', 'translate(' + slice.width() / 2 + ',' + slice.height() / 2 + ')scale(' + k + ')translate(' + -x + ',' + -y + ')');
    bigText.transition()
      .duration(750)
      .attr('transform', 'translate(0,0)translate(' + bigTextX + ',' + bigTextY + ')')
      .style('font-size', bigTextSize);
    resultText.transition()
      .duration(750)
      .attr('transform', 'translate(0,0)translate(' + resultTextX + ',' + resultTextY + ')');
  };

  const selectAndDisplayNameOfRegion = function (feature) {
    let name = '';
    if (feature && feature.properties) {
      if (feature.properties.ID_2) {
        name = feature.properties.NAME_2;
      } else {
        name = feature.properties.NAME_1;
      }
    }
    bigText.text(name);
  };

  const updateMetrics = function (region) {
    if (region.length > 0) {
      resultText.text(d3.format(',')(region[0].metric));
    }
  };

  const mouseenter = function (d) {
    // Darken color
    let c = colorFn(d);
    if (c !== 'none') {
      c = d3.rgb(c).darker().toString();
    }
    d3.select(this).style('fill', c);
    selectAndDisplayNameOfRegion(d);
    const result = data.filter(region => region.country_id === d.properties.ISO);
    updateMetrics(result);
  };

  const mouseout = function () {
    d3.select(this).style('fill', colorFn);
    bigText.text('');
    resultText.text('');
  };

  div.append('rect')
    .attr('class', 'background')
    .attr('width', slice.width())
    .attr('height', slice.height())
    .on('click', clicked);

  g = div.append('g');
  const mapLayer = g.append('g')
    .classed('map-layer', true);
  bigText = g.append('text')
    .classed('big-text', true)
    .attr('x', 20)
    .attr('y', 45);
  resultText = g.append('text')
    .classed('result-text', true)
    .attr('x', 20)
    .attr('y', 60);

  const url = `/static/assets/visualizations/countries/${fd.select_country.toLowerCase()}.geojson`;
  d3.json(url, function (error, mapData) {
    const features = mapData.features;
    const center = d3.geo.centroid(mapData);
    let scale = 150;
    let offset = [slice.width() / 2, slice.height() / 2];
    let projection = d3.geo.mercator().scale(scale).center(center)
      .translate(offset);

    path = path.projection(projection);

    const bounds = path.bounds(mapData);
    const hscale = scale * slice.width() / (bounds[1][0] - bounds[0][0]);
    const vscale = scale * slice.height() / (bounds[1][1] - bounds[0][1]);
    scale = (hscale < vscale) ? hscale : vscale;
    const offsetWidth = slice.width() - (bounds[0][0] + bounds[1][0]) / 2;
    const offsetHeigth = slice.height() - (bounds[0][1] + bounds[1][1]) / 2;
    offset = [offsetWidth, offsetHeigth];
    projection = d3.geo.mercator().center(center).scale(scale).translate(offset);
    path = path.projection(projection);

    // Draw each province as a path
    mapLayer.selectAll('path')
      .data(features)
      .enter().append('path')
      .attr('d', path)
      .attr('class', 'region')
      .attr('vector-effect', 'non-scaling-stroke')
      .style('fill', colorFn)
      .on('mouseenter', mouseenter)
      .on('mouseout', mouseout)
      .on('click', clicked);
  });
  container.show();
}

module.exports = countryMapChart;
<commit_msg>edit color scaler variable<commit_after>import d3 from 'd3';
import './country_map.css';
import { colorScalerFactory } from '../javascripts/modules/colors';
import d3ScaleChromatic from 'd3-scale-chromatic';

function countryMapChart(slice, payload) {
  // CONSTANTS
  const fd = payload.form_data;
  let path;
  let g;
  let bigText;
  let resultText;
  const container = slice.container;
  const data = payload.data;
  const viz = 'map';

  const colorScaler = colorScalerFactory(fd.linear_color_scheme, data, v => v.metric, viz);
  const colorMap = {};
  data.forEach((d) => {
    colorMap[d.country_id] = colorScaler(d.metric);
  });
  const colorFn = d => colorMap[d.properties.ISO] || 'none';

  let centered;
  path = d3.geo.path();
  d3.select(slice.selector).selectAll('*').remove();
  const div = d3.select(slice.selector)
    .append('svg:svg')
    .attr('width', slice.width())
    .attr('height', slice.height())
    .attr('preserveAspectRatio', 'xMidYMid meet');

  container.css('height', slice.height());
  container.css('width', slice.width());

  const clicked = function (d) {
    let x;
    let y;
    let k;
    let bigTextX;
    let bigTextY;
    let bigTextSize;
    let resultTextX;
    let resultTextY;

    if (d && centered !== d) {
      const centroid = path.centroid(d);
      x = centroid[0];
      y = centroid[1];
      bigTextX = centroid[0];
      bigTextY = centroid[1] - 40;
      resultTextX = centroid[0];
      resultTextY = centroid[1] - 40;
      bigTextSize = '6px';
      k = 4;
      centered = d;
    } else {
      x = slice.width() / 2;
      y = slice.height() / 2;
      bigTextX = 0;
      bigTextY = 0;
      resultTextX = 0;
      resultTextY = 0;
      bigTextSize = '30px';
      k = 1;
      centered = null;
    }

    g.transition()
      .duration(750)
      .attr('transform', 'translate(' + slice.width() / 2 + ',' + slice.height() / 2 + ')scale(' + k + ')translate(' + -x + ',' + -y + ')');
    bigText.transition()
      .duration(750)
      .attr('transform', 'translate(0,0)translate(' + bigTextX + ',' + bigTextY + ')')
      .style('font-size', bigTextSize);
    resultText.transition()
      .duration(750)
      .attr('transform', 'translate(0,0)translate(' + resultTextX + ',' + resultTextY + ')');
  };

  const selectAndDisplayNameOfRegion = function (feature) {
    let name = '';
    if (feature && feature.properties) {
      if (feature.properties.ID_2) {
        name = feature.properties.NAME_2;
      } else {
        name = feature.properties.NAME_1;
      }
    }
    bigText.text(name);
  };

  const updateMetrics = function (region) {
    if (region.length > 0) {
      resultText.text(d3.format(',')(region[0].metric));
    }
  };

  const mouseenter = function (d) {
    // Darken color
    let c = colorFn(d);
    if (c !== 'none') {
      c = d3.rgb(c).darker().toString();
    }
    d3.select(this).style('fill', c);
    selectAndDisplayNameOfRegion(d);
    const result = data.filter(region => region.country_id === d.properties.ISO);
    updateMetrics(result);
  };

  const mouseout = function () {
    d3.select(this).style('fill', colorFn);
    bigText.text('');
    resultText.text('');
  };

  div.append('rect')
    .attr('class', 'background')
    .attr('width', slice.width())
    .attr('height', slice.height())
    .on('click', clicked);

  g = div.append('g');
  const mapLayer = g.append('g')
    .classed('map-layer', true);
  bigText = g.append('text')
    .classed('big-text', true)
    .attr('x', 20)
    .attr('y', 45);
  resultText = g.append('text')
    .classed('result-text', true)
    .attr('x', 20)
    .attr('y', 60);

  const url = `/static/assets/visualizations/countries/${fd.select_country.toLowerCase()}.geojson`;
  d3.json(url, function (error, mapData) {
    const features = mapData.features;
    const center = d3.geo.centroid(mapData);
    let scale = 150;
    let offset = [slice.width() / 2, slice.height() / 2];
    let projection = d3.geo.mercator().scale(scale).center(center)
      .translate(offset);

    path = path.projection(projection);

    const bounds = path.bounds(mapData);
    const hscale = scale * slice.width() / (bounds[1][0] - bounds[0][0]);
    const vscale = scale * slice.height() / (bounds[1][1] - bounds[0][1]);
    scale = (hscale < vscale) ? hscale : vscale;
    const offsetWidth = slice.width() - (bounds[0][0] + bounds[1][0]) / 2;
    const offsetHeigth = slice.height() - (bounds[0][1] + bounds[1][1]) / 2;
    offset = [offsetWidth, offsetHeigth];
    projection = d3.geo.mercator().center(center).scale(scale).translate(offset);
    path = path.projection(projection);

    // Draw each province as a path
    mapLayer.selectAll('path')
      .data(features)
      .enter().append('path')
      .attr('d', path)
      .attr('class', 'region')
      .attr('vector-effect', 'non-scaling-stroke')
      .style('fill', colorFn)
      .on('mouseenter', mouseenter)
      .on('mouseout', mouseout)
      .on('click', clicked);
  });
  container.show();
}

module.exports = countryMapChart;
<|endoftext|>


<commit_before>/**
 * @module slimdom
 */
if (typeof define !== 'function') { var define = require('amdefine')(module); }
define(
	function() {
		'use strict';

		/**
		 * Get all parents of the given node.
		 *
		 * @method parents
		 *
		 * @param  {Node}   node  A Node to get it's parents from.
		 *
		 * @return {Array}  Array with all parents of node
		 */
		function parents(node) {
			var nodes = [];
			while (node) {
				nodes.unshift(node);
				node = node.parentNode;
			}
			return nodes;
		}

		/**
		 * Returns the first common ancestor of the two nodes.
		 *
		 * @method commonAncestor
		 *
		 * @param  {Node}  node1  First node for comparison.
		 * @param  {Node}  node2  Second node for comparison.
		 *
		 * @return {Node}  First common ancestor of the two nodes.
		 */
		function commonAncestor(node1, node2) {
			var parents1 = parents(node1),
				parents2 = parents(node2);
			if (parents1[0] != parents2[0]) return null;
			var commonParent = parents1[0];
			while (parents1[0] && parents2[0] && parents1[0] == parents2[0]) {
				commonParent = parents1.shift();
				parents2.shift();
			}
			return commonParent;
		}

		/**
		 * Compares two positions within the document.
		 *
		 * @method comparePoints
		 *
		 * @param  {Node}    node1    First node for comparison.
		 * @param  {Number}  offset1  Initial offset for first node.
		 * @param  {Node}    node2    Second node for comparison.
		 * @param  {Number}  offset2  Initial offset for second node.
		 *
		 * @return {Number}  Offset between node 1 and 2 expressed as a number.
		 */
		function comparePoints(node1, offset1, node2, offset2) {
			if (node1 !== node2) {
				var parents1 = parents(node1),
					parents2 = parents(node2);
				// This should not be called on nodes from different trees
				if (parents1[0] != parents2[0]) return undefined;
				// Skip common parents
				var commonParent = parents1[0];
				while (parents1[0] && parents2[0] && parents1[0] == parents2[0]) {
					commonParent = parents1.shift();
					parents2.shift();
				}
				// Compute offsets at the level under the last common parent,
				// we add 0.5 to indicate a position inside the parent rather than before or after
				if (parents1.length) offset1 = _.indexOf(commonParent.childNodes, parents1[0]) + 0.5;
				if (parents2.length) offset2 = _.indexOf(commonParent.childNodes, parents2[0]) + 0.5;
			}
			// Compare positions at this level
			return offset1 - offset2;
		}

		/**
		 * Queues mutation on all target nodes, and on all target nodes of all its ancestors.
		 *
		 * @method queueMutationRecord
		 *
		 * @param  {MutationRecord}  mutationRecord  The MutationRecord to queue.
		 */
		function queueMutationRecord(mutationRecord) {
			// Check all inclusive ancestors of the target for registered observers
			for (var node = mutationRecord.target; node; node = node.parentNode) {
				node.registeredObservers.queueRecord(mutationRecord);
			}
		}

		/**
		 * Slimdom utility functions, intended for internal usage by the slimdom module.
		 *
		 * @class util
		 * @static
		 * @private
		 */
		return {
			commonAncestor: commonAncestor,
			comparePoints: comparePoints,
			parents: parents,
			queueMutationRecord: queueMutationRecord
		};
	}
);
<commit_msg>Improve commonAncestor by using a single loop instead of two shift()s<commit_after>/**
 * @module slimdom
 */
if (typeof define !== 'function') { var define = require('amdefine')(module); }
define(
	function() {
		'use strict';

		/**
		 * Get all parents of the given node.
		 *
		 * @method parents
		 *
		 * @param  {Node}   node  A Node to get it's parents from.
		 *
		 * @return {Array}  Array with all parents of node
		 */
		function parents(node) {
			var nodes = [];
			while (node) {
				nodes.unshift(node);
				node = node.parentNode;
			}
			return nodes;
		}

		/**
		 * Returns the first common ancestor of the two nodes.
		 *
		 * @method commonAncestor
		 *
		 * @param  {Node}  node1  First node for comparison.
		 * @param  {Node}  node2  Second node for comparison.
		 *
		 * @return {Node}  First common ancestor of the two nodes.
		 */
		function commonAncestor(node1, node2) {
			var parents1 = parents(node1),
				parents2 = parents(node2),
				parent1 = parents1[0],
				parent2 = parents2[0];

			if (parent1 != parent2) return null;

			for (var i = 1, l = Math.min(parents1.length, parents2.length); i < l; i++){
				// Let the commonAncestor be one step behind
				var commonAncestor = parent1;
				parent1 = parents1[i];
				parent2 = parents2[i];

				if (!parent1 || !parent2 || parent1 != parent2)
					return commonAncestor;
			}
			// The common Ancestor is the node itself
			return parent1;
		}

		/**
		 * Compares two positions within the document.
		 *
		 * @method comparePoints
		 *
		 * @param  {Node}    node1    First node for comparison.
		 * @param  {Number}  offset1  Initial offset for first node.
		 * @param  {Node}    node2    Second node for comparison.
		 * @param  {Number}  offset2  Initial offset for second node.
		 *
		 * @return {Number}  Offset between node 1 and 2 expressed as a number.
		 */
		function comparePoints(node1, offset1, node2, offset2) {
			if (node1 !== node2) {
				var parents1 = parents(node1),
					parents2 = parents(node2);
				// This should not be called on nodes from different trees
				if (parents1[0] != parents2[0]) return undefined;
				// Skip common parents
				var commonParent = parents1[0];
				while (parents1[0] && parents2[0] && parents1[0] == parents2[0]) {
					commonParent = parents1.shift();
					parents2.shift();
				}
				// Compute offsets at the level under the last common parent,
				// we add 0.5 to indicate a position inside the parent rather than before or after
				if (parents1.length) offset1 = _.indexOf(commonParent.childNodes, parents1[0]) + 0.5;
				if (parents2.length) offset2 = _.indexOf(commonParent.childNodes, parents2[0]) + 0.5;
			}
			// Compare positions at this level
			return offset1 - offset2;
		}

		/**
		 * Queues mutation on all target nodes, and on all target nodes of all its ancestors.
		 *
		 * @method queueMutationRecord
		 *
		 * @param  {MutationRecord}  mutationRecord  The MutationRecord to queue.
		 */
		function queueMutationRecord(mutationRecord) {
			// Check all inclusive ancestors of the target for registered observers
			for (var node = mutationRecord.target; node; node = node.parentNode) {
				node.registeredObservers.queueRecord(mutationRecord);
			}
		}

		/**
		 * Slimdom utility functions, intended for internal usage by the slimdom module.
		 *
		 * @class util
		 * @static
		 * @private
		 */
		return {
			commonAncestor: commonAncestor,
			comparePoints: comparePoints,
			parents: parents,
			queueMutationRecord: queueMutationRecord
		};
	}
);
<|endoftext|>


<commit_before>/*
 * lib/util.js: general-purpose utility functions.  These should generally be
 * pushed into an existing external package.
 */

var mod_assert = require('assert');
var mod_fs = require('fs');
var mod_http = require('http');
var mod_jsprim = require('jsprim');
var mod_memorystream = require('memorystream');
var mod_path = require('path');
var mod_uuid = require('node-uuid');
var mod_util = require('util');
var mod_verror = require('verror');

var VError = mod_verror.VError;

exports.maRestifyPanic = maRestifyPanic;
exports.maHttpProxy = maHttpProxy;
exports.maWrapIgnoreError = maWrapIgnoreError;
exports.maIgnoreError = maIgnoreError;
exports.Throttler = Throttler;
exports.makeEtag = makeEtag;
exports.readConf = readConf;
exports.SaveGeneration = SaveGeneration;
exports.pathExtractFirst = pathExtractFirst;
exports.CVError = CVError;
exports.mantaSignNull = mantaSignNull;
exports.mantaFileSave = mantaFileSave;

function maRestifyPanic(request, response, route, err)
{
	request.log.fatal(err, 'FATAL ERROR handling %s %s', request.method,
	    request.url);
	throw (err);
}

/*
 * Forwards an HTTP request to another server for processing. Arguments:
 *
 * 	request, response	Local HTTP request, response
 *
 * 	server			Arguments describing the remote server, exactly
 * 				as passed to http.request(), including
 * 				socketPath, host, port, and so on.  Additional
 * 				headers may also be specified here.
 *
 *      continue		Function to be invoked either when 100-continue
 *      (optional)		is received from the server (if the "expect:
 *      			100-continue" header is set), or when the
 *      			response is received (if that header is not
 *      			set).
 *
 * Callback is invoked as usual as callback([err]).  This implementation assumes
 * a restify request and response, including "request.log" and "response.send"
 * methods.  In the event of error, the response is always completed by the time
 * the callback is invoked (i.e. it is not required (nor valid) to write the
 * error to the response from the callback).
 */
function maHttpProxy(args, callback)
{
	var request = args['request'];
	var response = args['response'];
	var server_args = mod_jsprim.deepCopy(args['server']);
	var continuefunc = args['continue'];
	var state, key, subrequest, checkDone;

	mod_assert.ok(request.log,
	    'expected "log" on request (not using restify?)');
	mod_assert.ok(response.send,
	    'expected "send" on response (not using restify?)');

	state = {
	    'summary': request.method + ' ' + request.url,
	    'error': false,
	    'wroteHeader': false,
	    'request_args': server_args
	};

	server_args['method'] = request.method;

	if (!server_args.hasOwnProperty('path'))
		server_args['path'] = request.url;

	if (!server_args.hasOwnProperty('headers'))
		server_args['headers'] = {};

	for (key in request.headers) {
		if (key.toLowerCase() == 'connection')
			continue;
		server_args['headers'][key] = request.headers[key];
	}

	checkDone = function (where, err) {
		request.log.trace('proxy: ' + where);

		if (state['error'])
			/*
			 * We've already emitted an error. Ignore subsequent
			 * errors and completion.
			 */
			return;

		if (!err) {
			callback();
			return;
		}

		state['error'] = true;
		request.log.error(err, 'error proxying request "%s": %s',
		    state['summary'], where);

		if (!state['wroteHeader']) {
			response.send(err);
			callback(err);
		} else {
			response.end();
		}
	};

	subrequest = mod_http.request(server_args);
	subrequest.on('error', checkDone.bind(null, 'subrequest error'));
	request.on('error', checkDone.bind(null, 'request error'));
	request.on('end', function () {
		request.log.trace('proxy: initial request end');
	});

	if (server_args['headers']['expect'] == '100-continue') {
		request.pause();

		subrequest.on('continue', function () {
			if (continuefunc) {
				continuefunc();
				continuefunc = null;
			}

			response.writeContinue();
			request.pipe(subrequest);
			request.resume();
		});
	} else {
		request.pipe(subrequest);
	}

	subrequest.on('response', function (subresponse) {
		request.log.trace('proxy: subresponse received');

		state['wroteHeader'] = true;
		response.writeHead(subresponse.statusCode, subresponse.headers);
		subresponse.pipe(response);
		subresponse.on('end', checkDone.bind(null, 'subresponse end'));
		subresponse.on('error',
		    checkDone.bind(null, 'subresponse error'));

		if (response.statusCode < 400 && continuefunc)
			continuefunc();
	});

	return (state);
}

/*
 * Wrap an asynchronous function with one that ignores a certain class of system
 * errors (or all errors).   More precisely, given an asynchronous function
 * invoked as
 *
 *    func([arg1[, arg2 ...]], callback)
 *
 * returns a wrapper function that invokes "func" with arg1..argN and a callback
 * that passes through all errors whose "code" in "codes".  "codes" may be
 * omitted entirely, in which case *all* errors are ignored.  The former is
 * useful for calls to unlink, where an ENOENT error is often okay.  The latter
 * is useful for calls to close, where most errors are not actionable.
 */
function maWrapIgnoreError(func, codes)
{
	return (function ignoreErr() {
		var args = Array.prototype.slice.call(arguments, 0,
		    arguments.length - 1);
		var callback = arguments[arguments.length - 1];

		args.push(maIgnoreError(callback, codes));
		func.apply(null, args);
	});
}

/*
 * Returns a wrapper for the given callback function that does NOT pass through
 * any errors whose code is one of "codes".  If "codes" is not specified, all
 * errors are ignored.
 */
function maIgnoreError(callback, codes)
{
	return (function maIgnoreCb(err) {
		if (!codes || !err || !('code' in err)) {
			callback();
			return;
		}

		for (var i = 0; i < codes.length; i++) {
			if (err['code'] == codes[i]) {
				callback();
				return;
			}
		}

		callback(err);
	});
}

/*
 * Simple interface for making sure an asynchronous operation doesn't occur more
 * frequently than the given interval.
 */
function Throttler(interval)
{
	this.p_interval = interval;
	this.p_start = undefined;
	this.p_done = undefined;
	this.p_ongoing = false;
}

Throttler.prototype.start = function ()
{
	mod_assert.ok(!this.p_ongoing);
	this.p_start = new Date();
	this.p_ongoing = true;
};

Throttler.prototype.done = function ()
{
	mod_assert.ok(this.p_ongoing);
	this.p_done = new Date();
	this.p_ongoing = false;
};

Throttler.prototype.tooRecent = function ()
{
	if (this.p_ongoing)
		return (true);

	if (this.p_done && Date.now() - this.p_done.getTime() < this.p_interval)
		/* Last request was too recent to try again. */
		return (true);

	return (false);
};

Throttler.prototype.ongoing = function ()
{
	return (this.p_ongoing);
};

/*
 * Make up an arbitrary etag for the given object.
 */
function makeEtag(obj)
{
	return (mod_uuid.v4());
}

/*
 * Synchronously reads a JSON file and validates that it matches the given
 * schema.
 */
function readConf(log, schema, filename)
{
	var contents, json, error;

	try {
		contents = mod_fs.readFileSync(filename);
		json = JSON.parse(contents);
		error = mod_jsprim.validateJsonObject(schema, json);

		if (error)
			throw (error);
	} catch (ex) {
		log.fatal(ex, 'failed to read configuration');
		throw (ex);
	}

	return (json);
}

/*
 * Keeps track of objects with "dirty" and "saved" generation numbers and an
 * asynchronous operation that updates them.
 */
function SaveGeneration()
{
	this.s_dirty = 0;
	this.s_saved = 0;
	this.s_pending = undefined;
}

SaveGeneration.prototype.markDirty = function ()
{
	this.s_dirty++;
};

SaveGeneration.prototype.saveStart = function ()
{
	mod_assert.ok(this.s_pending === undefined,
	    'attempted concurrent saves');
	this.s_pending = this.s_dirty;
};

SaveGeneration.prototype.saveOk = function ()
{
	mod_assert.ok(this.s_pending !== undefined,
	    'no save operation pending');
	this.s_saved = this.s_pending;
	this.s_pending = undefined;
};

SaveGeneration.prototype.saveFailed = function ()
{
	mod_assert.ok(this.s_pending !== undefined,
	    'no save operation pending');
	this.s_pending = undefined;
};

SaveGeneration.prototype.dirty = function ()
{
	return (this.s_dirty > this.s_saved);
};

SaveGeneration.prototype.pending = function ()
{
	return (this.s_pending !== undefined);
};

/*
 * Extracts the first component of "path".  This is used to extract either the
 * account uuid or the login name from an input key.
 */
function pathExtractFirst(path)
{
	var i, j;

	i = path.indexOf('/');
	j = path.indexOf('/', i + 1);
	return (path.substr(i + 1, j - i - 1));
}

/*
 * Common error class that supports a "code" field.
 */
function CVError(code, options)
{
	var args = Array.prototype.slice.call(arguments, 1);
	VError.apply(this, args);
	this.code = code;
}

mod_util.inherits(CVError, VError);
CVError.prototype.name = 'CVError';

/*
 * node-manta "sign" function that produces no signature.  This should only be
 * used when also specifying an authToken, in which case muskie doesn't need a
 * signature.
 */
function mantaSignNull(_, callback)
{
	callback(null, null);
}

/*
 * Upload a file to Manta.  Automatically does "mkdir -p" if the parent
 * directory does not exist.  Arguments:
 *
 *    client		Manta client object
 *
 *    filename		Path of file to upload.  This file should not be
 *    			modified during this operation.  (This function is only
 *    			called by consumers who know there is nothing else
 *    			operating on the file.)
 *
 *    key		Object key to create
 *
 *    headers		Additional headers for PUT request
 *
 *    log		Bunyan logger
 *
 *    stderr		Boolean indicating this is a stderr stream
 */
function mantaFileSave(args, callback, noretry)
{
	mod_assert.ok(args['client'], '"client" is required');
	mod_assert.ok(args['log'], '"log" is required');
	mod_assert.equal(typeof (args['filename']), 'string',
	    '"filename" must be a string');
	mod_assert.equal(typeof (args['key']), 'string',
	    '"key" must be a string');

	var filename = args['filename'];

	mod_fs.stat(filename, function (err, stat) {
		if (err) {
			callback(new VError(err, 'stat "%s"', filename));
			return;
		}

		var instream;

		if (stat['size'] === 0) {
			instream = new mod_memorystream('',
			    { 'writable': false });
			mantaStreamSave({
			    'client': args['client'],
			    'stream': instream,
			    'size': 0,
			    'key': args['key'],
			    'headers': args['headers'],
			    'stderr': args['stderr']
			}, callback, noretry);
			return;
		}

		var opened = false;
		instream = mod_fs.createReadStream(filename);

		instream.on('error', function (suberr) {
			callback(new VError(suberr, '%s "%s"',
			    opened ? 'read' : 'open', filename));
		});

		instream.on('open', function () {
			opened = true;

			mantaStreamSave({
			    'client': args['client'],
			    'stream': instream,
			    'size': stat['size'],
			    'key': args['key'],
			    'headers': args['headers'],
			    'stderr': args['stderr']
			}, callback, noretry);
		});
	});
}

function mantaStreamSave(args, callback, noretry)
{
	mod_assert.ok(args['client'], '"client" is required');
	mod_assert.ok(args['stream'], '"stream" is required');
	mod_assert.equal(typeof (args['size']), 'number',
	    '"size" must be a number');
	mod_assert.equal(typeof (args['key']), 'string',
	    '"key" must be a string');

	var client = args['client'];
	var key = args['key'];

	var options = {
	    'size': args['size'],
	    'headers': {}
	};

	if (args['headers']) {
		for (var header in args['headers'])
			options['headers'][header] = args['headers'][header];
	}

	if (args['stderr'])
		options['headers']['x-marlin-stream'] = 'stderr';

	client.put(key, args['stream'], options, function (err) {
		if (!err) {
			callback();
			return;
		}

		if (noretry || err.name != 'DirectoryDoesNotExistError') {
			callback(err);
			return;
		}

		client.mkdirp(mod_path.dirname(key), function (err2) {
			if (err2) {
				callback(new VError(err2, 'failed to mkdirp'));
				return;
			}

			mantaStreamSave(args, callback, true);
		});
	});
}
<commit_msg>MANTA-788 maHttpProxy doesn't clean up properly when downstream goes away<commit_after>/*
 * lib/util.js: general-purpose utility functions.  These should generally be
 * pushed into an existing external package.
 */

var mod_assert = require('assert');
var mod_fs = require('fs');
var mod_http = require('http');
var mod_jsprim = require('jsprim');
var mod_memorystream = require('memorystream');
var mod_path = require('path');
var mod_uuid = require('node-uuid');
var mod_util = require('util');
var mod_verror = require('verror');

var VError = mod_verror.VError;

exports.maRestifyPanic = maRestifyPanic;
exports.maHttpProxy = maHttpProxy;
exports.maWrapIgnoreError = maWrapIgnoreError;
exports.maIgnoreError = maIgnoreError;
exports.Throttler = Throttler;
exports.makeEtag = makeEtag;
exports.readConf = readConf;
exports.SaveGeneration = SaveGeneration;
exports.pathExtractFirst = pathExtractFirst;
exports.CVError = CVError;
exports.mantaSignNull = mantaSignNull;
exports.mantaFileSave = mantaFileSave;

function maRestifyPanic(request, response, route, err)
{
	request.log.fatal(err, 'FATAL ERROR handling %s %s', request.method,
	    request.url);
	throw (err);
}

/*
 * Forwards an HTTP request to another server for processing. Arguments:
 *
 * 	request, response	Local HTTP request, response
 *
 * 	server			Arguments describing the remote server, exactly
 * 				as passed to http.request(), including
 * 				socketPath, host, port, and so on.  Additional
 * 				headers may also be specified here.
 *
 *      continue		Function to be invoked either when 100-continue
 *      (optional)		is received from the server (if the "expect:
 *      			100-continue" header is set), or when the
 *      			response is received (if that header is not
 *      			set).
 *
 * Callback is invoked as usual as callback([err]).  This implementation assumes
 * a restify request and response, including "request.log" and "response.send"
 * methods.  In the event of error, the response is always completed by the time
 * the callback is invoked (i.e. it is not required (nor valid) to write the
 * error to the response from the callback).
 */
function maHttpProxy(args, callback)
{
	var request = args['request'];
	var response = args['response'];
	var server_args = mod_jsprim.deepCopy(args['server']);
	var continuefunc = args['continue'];
	var state, key, subrequest, checkDone;

	mod_assert.ok(request.log,
	    'expected "log" on request (not using restify?)');
	mod_assert.ok(response.send,
	    'expected "send" on response (not using restify?)');

	state = {
	    'summary': request.method + ' ' + request.url,
	    'error': false,
	    'wroteHeader': false,
	    'request_args': server_args
	};

	server_args['method'] = request.method;

	if (!server_args.hasOwnProperty('path'))
		server_args['path'] = request.url;

	if (!server_args.hasOwnProperty('headers'))
		server_args['headers'] = {};

	for (key in request.headers) {
		if (key.toLowerCase() == 'connection')
			continue;
		server_args['headers'][key] = request.headers[key];
	}

	checkDone = function (where, err) {
		request.log.trace('proxy: ' + where);

		if (state['error'])
			/*
			 * We've already emitted an error. Ignore subsequent
			 * errors and completion.
			 */
			return;

		if (!err) {
			callback();
			return;
		}

		state['error'] = true;
		request.log.error(err, 'error proxying request "%s": %s',
		    state['summary'], where);

		if (!state['wroteHeader']) {
			response.send(err);
			callback(err);
		} else {
			response.end();
		}
	};

	subrequest = mod_http.request(server_args);
	subrequest.on('error', checkDone.bind(null, 'subrequest error'));
	request.on('error', checkDone.bind(null, 'request error'));
	request.on('end', function () {
		request.log.trace('proxy: initial request end');
	});
	response.on('close', function () {
		request.log.error('response closed unexpectedly');
		subrequest.destroy();
	});

	if (server_args['headers']['expect'] == '100-continue') {
		request.pause();

		subrequest.on('continue', function () {
			if (continuefunc) {
				continuefunc();
				continuefunc = null;
			}

			response.writeContinue();
			request.pipe(subrequest);
			request.resume();
		});
	} else {
		request.pipe(subrequest);
	}

	subrequest.on('response', function (subresponse) {
		request.log.trace('proxy: subresponse received');

		state['wroteHeader'] = true;
		response.writeHead(subresponse.statusCode, subresponse.headers);
		subresponse.pipe(response);
		subresponse.on('end', checkDone.bind(null, 'subresponse end'));
		subresponse.on('error',
		    checkDone.bind(null, 'subresponse error'));

		if (response.statusCode < 400 && continuefunc)
			continuefunc();

		response.on('close', function () {
			request.log.error('response closed unexpectedly');
			subresponse.destroy();
		});
	});

	return (state);
}

/*
 * Wrap an asynchronous function with one that ignores a certain class of system
 * errors (or all errors).   More precisely, given an asynchronous function
 * invoked as
 *
 *    func([arg1[, arg2 ...]], callback)
 *
 * returns a wrapper function that invokes "func" with arg1..argN and a callback
 * that passes through all errors whose "code" in "codes".  "codes" may be
 * omitted entirely, in which case *all* errors are ignored.  The former is
 * useful for calls to unlink, where an ENOENT error is often okay.  The latter
 * is useful for calls to close, where most errors are not actionable.
 */
function maWrapIgnoreError(func, codes)
{
	return (function ignoreErr() {
		var args = Array.prototype.slice.call(arguments, 0,
		    arguments.length - 1);
		var callback = arguments[arguments.length - 1];

		args.push(maIgnoreError(callback, codes));
		func.apply(null, args);
	});
}

/*
 * Returns a wrapper for the given callback function that does NOT pass through
 * any errors whose code is one of "codes".  If "codes" is not specified, all
 * errors are ignored.
 */
function maIgnoreError(callback, codes)
{
	return (function maIgnoreCb(err) {
		if (!codes || !err || !('code' in err)) {
			callback();
			return;
		}

		for (var i = 0; i < codes.length; i++) {
			if (err['code'] == codes[i]) {
				callback();
				return;
			}
		}

		callback(err);
	});
}

/*
 * Simple interface for making sure an asynchronous operation doesn't occur more
 * frequently than the given interval.
 */
function Throttler(interval)
{
	this.p_interval = interval;
	this.p_start = undefined;
	this.p_done = undefined;
	this.p_ongoing = false;
}

Throttler.prototype.start = function ()
{
	mod_assert.ok(!this.p_ongoing);
	this.p_start = new Date();
	this.p_ongoing = true;
};

Throttler.prototype.done = function ()
{
	mod_assert.ok(this.p_ongoing);
	this.p_done = new Date();
	this.p_ongoing = false;
};

Throttler.prototype.tooRecent = function ()
{
	if (this.p_ongoing)
		return (true);

	if (this.p_done && Date.now() - this.p_done.getTime() < this.p_interval)
		/* Last request was too recent to try again. */
		return (true);

	return (false);
};

Throttler.prototype.ongoing = function ()
{
	return (this.p_ongoing);
};

/*
 * Make up an arbitrary etag for the given object.
 */
function makeEtag(obj)
{
	return (mod_uuid.v4());
}

/*
 * Synchronously reads a JSON file and validates that it matches the given
 * schema.
 */
function readConf(log, schema, filename)
{
	var contents, json, error;

	try {
		contents = mod_fs.readFileSync(filename);
		json = JSON.parse(contents);
		error = mod_jsprim.validateJsonObject(schema, json);

		if (error)
			throw (error);
	} catch (ex) {
		log.fatal(ex, 'failed to read configuration');
		throw (ex);
	}

	return (json);
}

/*
 * Keeps track of objects with "dirty" and "saved" generation numbers and an
 * asynchronous operation that updates them.
 */
function SaveGeneration()
{
	this.s_dirty = 0;
	this.s_saved = 0;
	this.s_pending = undefined;
}

SaveGeneration.prototype.markDirty = function ()
{
	this.s_dirty++;
};

SaveGeneration.prototype.saveStart = function ()
{
	mod_assert.ok(this.s_pending === undefined,
	    'attempted concurrent saves');
	this.s_pending = this.s_dirty;
};

SaveGeneration.prototype.saveOk = function ()
{
	mod_assert.ok(this.s_pending !== undefined,
	    'no save operation pending');
	this.s_saved = this.s_pending;
	this.s_pending = undefined;
};

SaveGeneration.prototype.saveFailed = function ()
{
	mod_assert.ok(this.s_pending !== undefined,
	    'no save operation pending');
	this.s_pending = undefined;
};

SaveGeneration.prototype.dirty = function ()
{
	return (this.s_dirty > this.s_saved);
};

SaveGeneration.prototype.pending = function ()
{
	return (this.s_pending !== undefined);
};

/*
 * Extracts the first component of "path".  This is used to extract either the
 * account uuid or the login name from an input key.
 */
function pathExtractFirst(path)
{
	var i, j;

	i = path.indexOf('/');
	j = path.indexOf('/', i + 1);
	return (path.substr(i + 1, j - i - 1));
}

/*
 * Common error class that supports a "code" field.
 */
function CVError(code, options)
{
	var args = Array.prototype.slice.call(arguments, 1);
	VError.apply(this, args);
	this.code = code;
}

mod_util.inherits(CVError, VError);
CVError.prototype.name = 'CVError';

/*
 * node-manta "sign" function that produces no signature.  This should only be
 * used when also specifying an authToken, in which case muskie doesn't need a
 * signature.
 */
function mantaSignNull(_, callback)
{
	callback(null, null);
}

/*
 * Upload a file to Manta.  Automatically does "mkdir -p" if the parent
 * directory does not exist.  Arguments:
 *
 *    client		Manta client object
 *
 *    filename		Path of file to upload.  This file should not be
 *    			modified during this operation.  (This function is only
 *    			called by consumers who know there is nothing else
 *    			operating on the file.)
 *
 *    key		Object key to create
 *
 *    headers		Additional headers for PUT request
 *
 *    log		Bunyan logger
 *
 *    stderr		Boolean indicating this is a stderr stream
 */
function mantaFileSave(args, callback, noretry)
{
	mod_assert.ok(args['client'], '"client" is required');
	mod_assert.ok(args['log'], '"log" is required');
	mod_assert.equal(typeof (args['filename']), 'string',
	    '"filename" must be a string');
	mod_assert.equal(typeof (args['key']), 'string',
	    '"key" must be a string');

	var filename = args['filename'];

	mod_fs.stat(filename, function (err, stat) {
		if (err) {
			callback(new VError(err, 'stat "%s"', filename));
			return;
		}

		var instream;

		if (stat['size'] === 0) {
			instream = new mod_memorystream('',
			    { 'writable': false });
			mantaStreamSave({
			    'client': args['client'],
			    'stream': instream,
			    'size': 0,
			    'key': args['key'],
			    'headers': args['headers'],
			    'stderr': args['stderr']
			}, callback, noretry);
			return;
		}

		var opened = false;
		instream = mod_fs.createReadStream(filename);

		instream.on('error', function (suberr) {
			callback(new VError(suberr, '%s "%s"',
			    opened ? 'read' : 'open', filename));
		});

		instream.on('open', function () {
			opened = true;

			mantaStreamSave({
			    'client': args['client'],
			    'stream': instream,
			    'size': stat['size'],
			    'key': args['key'],
			    'headers': args['headers'],
			    'stderr': args['stderr']
			}, callback, noretry);
		});
	});
}

function mantaStreamSave(args, callback, noretry)
{
	mod_assert.ok(args['client'], '"client" is required');
	mod_assert.ok(args['stream'], '"stream" is required');
	mod_assert.equal(typeof (args['size']), 'number',
	    '"size" must be a number');
	mod_assert.equal(typeof (args['key']), 'string',
	    '"key" must be a string');

	var client = args['client'];
	var key = args['key'];

	var options = {
	    'size': args['size'],
	    'headers': {}
	};

	if (args['headers']) {
		for (var header in args['headers'])
			options['headers'][header] = args['headers'][header];
	}

	if (args['stderr'])
		options['headers']['x-marlin-stream'] = 'stderr';

	client.put(key, args['stream'], options, function (err) {
		if (!err) {
			callback();
			return;
		}

		if (noretry || err.name != 'DirectoryDoesNotExistError') {
			callback(err);
			return;
		}

		client.mkdirp(mod_path.dirname(key), function (err2) {
			if (err2) {
				callback(new VError(err2, 'failed to mkdirp'));
				return;
			}

			mantaStreamSave(args, callback, true);
		});
	});
}
<|endoftext|>


<commit_before>var Q = require('q'),
    QW = require('q-wrap'),
    PATH = require('./path'),
    FS = require('fs');

exports.chdirOptParse = function() {
    return this.opt()
        .name('dir').short('C').long('chdir')
        .title('рабочая директория, по умолчанию текущая')
        .def(process.cwd())
        .val(function(d) {
            d = PATH.join(d, PATH.dirSep);
            process.chdir(d);
            return d;
        })
        .end();
};

exports.techsOptParse = function() {
    return this
        .opt()
            .name('addTech').short('t').long('add-tech')
            .title('добавить технологию')
            .arr()
            .end()
        .opt()
            .name('forceTech').short('T').long('force-tech')
            .title('использовать только эту технологию')
            .arr()
            .end()
        .opt()
            .name('noTech').short('n').long('no-tech')
            .title('исключить технологию из использования')
            .arr()
            .end()
};

exports.levelOptParse = function() {
    var def = exports.findLevel(process.cwd()),
        rel = PATH.relative(PATH.join(process.cwd(), PATH.dirSep), def);
    return this.opt()
        .name('levelDir').short('l').long('level')
        .def(def)
        .title(['директория уровня переопределения, по умолчанию: ',
            !rel? '.' : rel, PATH.dirSep].join(''))
        .val(function(d) { return PATH.join(d, PATH.dirSep) })
        .end();
};

exports.mergeTechs = function(level, opts) {
    // NOTE: если при создании блока/элемента/модификатора
    // указали --force-tech <name> или --no-tech, и в level.js
    // определена технология с таким именем/файлом на диске,
    // нужно использовать именно её
    var techs = opts.forceTech? {} : level.techs,
        optsTechs = [];

    opts.forceTech && optsTechs.push.apply(optsTechs, opts.forceTech);
    opts.addTech && optsTechs.push.apply(optsTechs, opts.addTech);

    optsTechs.forEach(function(t) {
        var tech = level.getTech(t),
            name = tech.getTechName();
        techs[name] || (techs[name] = tech);
    });

    opts.noTech && opts.noTech.forEach(function(t) {
        delete techs[level.getTech(t).getTechName()];
    });

    return techs;
};

exports.write = function(path, content) {
    FS.writeFileSync(path, Array.isArray(content) ? content.join('') : content);
};

exports.writeFile = function(path, content) {
    return Q.when(content, function(content) {
        return QW.execute(FS.writeFile, path, Array.isArray(content) ? content.join('') : content, 'utf8');
    });
};

exports.readFile = function(path) {
    return QW.execute(FS.readFile, path, 'utf8');
};

exports.mkdir = function(path) {
    try { FS.mkdirSync(path, 0777) } catch(ignore) {}
};

exports.mkdirs = function(path) {
    if (PATH.existsSync(path)) return;
    exports.mkdirs(PATH.dirname(path));
    exports.mkdir(path);
};

exports.isExists = function(path) {
    var d = Q.defer();
    PATH.exists(path, function(res) {
        d.resolve(res);
    });
    return d.promise;
};

exports.isFile = function(path) {
    try {
        return FS.statSync(path).isFile();
    } catch(ignore) {}
    return false;
};

exports.isFileP = function(path) {
    return Q.when(QW.execute(FS.stat, path),
        function(stat) {
            return stat.isFile();
        },
        function(err) {
            return false;
        });
};

exports.isDirectory = function(path) {
    try {
        return FS.statSync(path).isDirectory();
    } catch(ignore) {}
    return false;
};

exports.isLevel = function(path) {
    return exports.isDirectory(path) &&
        exports.isFile(PATH.join(path, '.bem', 'level.js'));
};

exports.findLevel = function(path, startPath) {
    startPath = startPath || path;
    if (exports.isLevel(path)) return path;
    if (PATH.isRoot(path)) return startPath;
    return exports.findLevel(PATH.dirname(path), startPath);
};

exports.fsWalkTree = function(root, fileCb, filterCb) {
    var files = FS.readdirSync(root);
    files.sort();
    while (files.length > 0) {
        var path = PATH.join(root, files.shift());
        if(filterCb && !filterCb(path)) continue;
        fileCb(path);
        if(exports.isDirectory(path)) exports.fsWalkTree(path, fileCb, filterCb);
    }
};

exports.getDirs = function(path) {
    try {
        return exports.isDirectory(path)?
            FS.readdirSync(path)
                .filter(function(d) {
                    return !(/^\.svn$/.test(d)) && exports.isDirectory(PATH.join(path, d));
                })
                .sort() :
            [];
    } catch (e) {
        return [];
    }
};

exports.getFiles = function(path) {
    try {
        return exports.isDirectory(path)?
            FS.readdirSync(path)
                .filter(function(f) {
                    return exports.isFile(PATH.join(path, f));
                })
                .sort() :
            [];
    } catch (e) {
        return [];
    }
};

exports.isEmptyObject = function(obj) {
    for(var i in obj) return false;
    return true;
};

exports.isRequireError = function(e) {
    return /^Cannot find module/.test(e.message);
};

exports.isPath = function(str) {
    return str.indexOf(PATH.dirSep) !== -1;
};

exports.isRequireable = function(path) {
    try {
        require.resolve(path);
        return true;
    } catch (e) {
        if(! exports.isRequireError(e)) throw e;
        return false;
    }
};

exports.arrayUnique = function(arr) {
    return arr.reduce(function(prev, cur) {
        if(prev.indexOf(cur) + 1) return prev;
        return prev.concat([cur]);
    }, []);
};

exports.arrayReverse = function(arr) {
    return arr.reduceRight(function(prev, cur) {
        prev.push(cur);
        return prev;
    }, []);
};

exports.getBemTechPath = function(name) {
    var bemTechs = PATH.unixToOs('bem/lib/techs'),
        path = PATH.join(bemTechs, name);
    if(exports.isRequireable(path)) {
        return path;
    }
    return PATH.unixToOs('bem/lib/tech');
};

exports.stripModuleExt = function(path) {
    var exts = Object.keys(require.extensions).map(function(v) {
        return v.replace(/^\./, '');
    });
    return path.replace(new RegExp('\\.(' + exts.join('|') + ')$'), '');
};

exports.getNodePaths = function() {
    return (process.env.NODE_PATH || '').split(PATH.pathSep);
};

exports.mergeDecls = function mergeDecls(d1, d2) {
    var keys = {};
    d1?
        d1.forEach(function(o) { keys[o.name || o] = o }) :
        d1 = [];

    d2.forEach(function(o2) {
        var name = o2.name || o2;
        if (keys.hasOwnProperty(name)) {
            var o1 = keys[name];
            o2.elems && (o1.elems = mergeDecls(o1.elems, o2.elems));
            o2.mods && (o1.mods = mergeDecls(o1.mods, o2.mods));
            o2.vals && (o1.vals = mergeDecls(o1.vals, o2.vals));
            o2.techs && (o1.techs = mergeDecls(o1.techs, o2.techs));
        } else {
            d1.push(o2);
            keys[name] = o2;
        }
    });

    return d1;
};

/**
 * Adopted from jquery's extend method. Under the terms of MIT License.
 *
 * http://code.jquery.com/jquery-1.4.2.js
 *
 * Modified by mscdex to use Array.isArray instead of the custom isArray method
 */
var extend = exports.extend = function() {
    // copy reference to target object
    var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy;

    // Handle a deep copy situation
    if (typeof target === 'boolean') {
        deep = target;
        target = arguments[1] || {};
        // skip the boolean and the target
        i = 2;
    }

    // Handle case when target is a string or something (possible in deep copy)
    if (typeof target !== 'object' && !typeof target === 'function')
        target = {};

    var isPlainObject = function(obj) {
        // Must be an Object.
        // Because of IE, we also have to check the presence of the constructor property.
        // Make sure that DOM nodes and window objects don't pass through, as well
        if (!obj || toString.call(obj) !== '[object Object]' || obj.nodeType || obj.setInterval)
            return false;

        var has_own_constructor = hasOwnProperty.call(obj, 'constructor');
        var has_is_property_of_method = hasOwnProperty.call(obj.constructor.prototype, 'isPrototypeOf');
        // Not own constructor property must be Object
        if (obj.constructor && !has_own_constructor && !has_is_property_of_method)
            return false;

        // Own properties are enumerated firstly, so to speed up,
        // if last one is own, then all properties are own.

        var key, last_key;
        for (key in obj)
            last_key = key;

        return typeof last_key === 'undefined' || hasOwnProperty.call(obj, last_key);
    };

    for (; i < length; i++) {
        // Only deal with non-null/undefined values
        if ((options = arguments[i]) !== null) {
            // Extend the base object
            for (name in options) {
                if (!options.hasOwnProperty(name))
                    continue;
                src = target[name];
                copy = options[name];

                // Prevent never-ending loop
                if (target === copy)
                    continue;

                // Recurse if we're merging object literal values or arrays
                if (deep && copy && (isPlainObject(copy) || Array.isArray(copy))) {
                    var clone = src && (isPlainObject(src) || Array.isArray(src)) ? src : Array.isArray(copy) ? [] : {};

                    // Never move original objects, clone them
                    target[name] = extend(deep, clone, copy);

                // Don't bring in undefined values
                } else if (typeof copy !== 'undefined')
                    target[name] = copy;
            }
        }
    }

    // Return the modified object
    return target;
};
<commit_msg>Add `bemForEach()` iterator to `lib/util`<commit_after>var Q = require('q'),
    QW = require('q-wrap'),
    PATH = require('./path'),
    FS = require('fs');

exports.chdirOptParse = function() {
    return this.opt()
        .name('dir').short('C').long('chdir')
        .title('рабочая директория, по умолчанию текущая')
        .def(process.cwd())
        .val(function(d) {
            d = PATH.join(d, PATH.dirSep);
            process.chdir(d);
            return d;
        })
        .end();
};

exports.techsOptParse = function() {
    return this
        .opt()
            .name('addTech').short('t').long('add-tech')
            .title('добавить технологию')
            .arr()
            .end()
        .opt()
            .name('forceTech').short('T').long('force-tech')
            .title('использовать только эту технологию')
            .arr()
            .end()
        .opt()
            .name('noTech').short('n').long('no-tech')
            .title('исключить технологию из использования')
            .arr()
            .end()
};

exports.levelOptParse = function() {
    var def = exports.findLevel(process.cwd()),
        rel = PATH.relative(PATH.join(process.cwd(), PATH.dirSep), def);
    return this.opt()
        .name('levelDir').short('l').long('level')
        .def(def)
        .title(['директория уровня переопределения, по умолчанию: ',
            !rel? '.' : rel, PATH.dirSep].join(''))
        .val(function(d) { return PATH.join(d, PATH.dirSep) })
        .end();
};

exports.mergeTechs = function(level, opts) {
    // NOTE: если при создании блока/элемента/модификатора
    // указали --force-tech <name> или --no-tech, и в level.js
    // определена технология с таким именем/файлом на диске,
    // нужно использовать именно её
    var techs = opts.forceTech? {} : level.techs,
        optsTechs = [];

    opts.forceTech && optsTechs.push.apply(optsTechs, opts.forceTech);
    opts.addTech && optsTechs.push.apply(optsTechs, opts.addTech);

    optsTechs.forEach(function(t) {
        var tech = level.getTech(t),
            name = tech.getTechName();
        techs[name] || (techs[name] = tech);
    });

    opts.noTech && opts.noTech.forEach(function(t) {
        delete techs[level.getTech(t).getTechName()];
    });

    return techs;
};

exports.write = function(path, content) {
    FS.writeFileSync(path, Array.isArray(content) ? content.join('') : content);
};

exports.writeFile = function(path, content) {
    return Q.when(content, function(content) {
        return QW.execute(FS.writeFile, path, Array.isArray(content) ? content.join('') : content, 'utf8');
    });
};

exports.readFile = function(path) {
    return QW.execute(FS.readFile, path, 'utf8');
};

exports.mkdir = function(path) {
    try { FS.mkdirSync(path, 0777) } catch(ignore) {}
};

exports.mkdirs = function(path) {
    if (PATH.existsSync(path)) return;
    exports.mkdirs(PATH.dirname(path));
    exports.mkdir(path);
};

exports.isExists = function(path) {
    var d = Q.defer();
    PATH.exists(path, function(res) {
        d.resolve(res);
    });
    return d.promise;
};

exports.isFile = function(path) {
    try {
        return FS.statSync(path).isFile();
    } catch(ignore) {}
    return false;
};

exports.isFileP = function(path) {
    return Q.when(QW.execute(FS.stat, path),
        function(stat) {
            return stat.isFile();
        },
        function(err) {
            return false;
        });
};

exports.isDirectory = function(path) {
    try {
        return FS.statSync(path).isDirectory();
    } catch(ignore) {}
    return false;
};

exports.isLevel = function(path) {
    return exports.isDirectory(path) &&
        exports.isFile(PATH.join(path, '.bem', 'level.js'));
};

exports.findLevel = function(path, startPath) {
    startPath = startPath || path;
    if (exports.isLevel(path)) return path;
    if (PATH.isRoot(path)) return startPath;
    return exports.findLevel(PATH.dirname(path), startPath);
};

exports.fsWalkTree = function(root, fileCb, filterCb) {
    var files = FS.readdirSync(root);
    files.sort();
    while (files.length > 0) {
        var path = PATH.join(root, files.shift());
        if(filterCb && !filterCb(path)) continue;
        fileCb(path);
        if(exports.isDirectory(path)) exports.fsWalkTree(path, fileCb, filterCb);
    }
};

exports.getDirs = function(path) {
    try {
        return exports.isDirectory(path)?
            FS.readdirSync(path)
                .filter(function(d) {
                    return !(/^\.svn$/.test(d)) && exports.isDirectory(PATH.join(path, d));
                })
                .sort() :
            [];
    } catch (e) {
        return [];
    }
};

exports.getFiles = function(path) {
    try {
        return exports.isDirectory(path)?
            FS.readdirSync(path)
                .filter(function(f) {
                    return exports.isFile(PATH.join(path, f));
                })
                .sort() :
            [];
    } catch (e) {
        return [];
    }
};

exports.isEmptyObject = function(obj) {
    for(var i in obj) return false;
    return true;
};

exports.isRequireError = function(e) {
    return /^Cannot find module/.test(e.message);
};

exports.isPath = function(str) {
    return str.indexOf(PATH.dirSep) !== -1;
};

exports.isRequireable = function(path) {
    try {
        require.resolve(path);
        return true;
    } catch (e) {
        if(! exports.isRequireError(e)) throw e;
        return false;
    }
};

exports.arrayUnique = function(arr) {
    return arr.reduce(function(prev, cur) {
        if(prev.indexOf(cur) + 1) return prev;
        return prev.concat([cur]);
    }, []);
};

exports.arrayReverse = function(arr) {
    return arr.reduceRight(function(prev, cur) {
        prev.push(cur);
        return prev;
    }, []);
};

exports.getBemTechPath = function(name) {
    var bemTechs = PATH.unixToOs('bem/lib/techs'),
        path = PATH.join(bemTechs, name);
    if(exports.isRequireable(path)) {
        return path;
    }
    return PATH.unixToOs('bem/lib/tech');
};

exports.stripModuleExt = function(path) {
    var exts = Object.keys(require.extensions).map(function(v) {
        return v.replace(/^\./, '');
    });
    return path.replace(new RegExp('\\.(' + exts.join('|') + ')$'), '');
};

exports.getNodePaths = function() {
    return (process.env.NODE_PATH || '').split(PATH.pathSep);
};

exports.mergeDecls = function mergeDecls(d1, d2) {
    var keys = {};
    d1?
        d1.forEach(function(o) { keys[o.name || o] = o }) :
        d1 = [];

    d2.forEach(function(o2) {
        var name = o2.name || o2;
        if (keys.hasOwnProperty(name)) {
            var o1 = keys[name];
            o2.elems && (o1.elems = mergeDecls(o1.elems, o2.elems));
            o2.mods && (o1.mods = mergeDecls(o1.mods, o2.mods));
            o2.vals && (o1.vals = mergeDecls(o1.vals, o2.vals));
            o2.techs && (o1.techs = mergeDecls(o1.techs, o2.techs));
        } else {
            d1.push(o2);
            keys[name] = o2;
        }
    });

    return d1;
};

exports.bemForEach = function(decl, cb) {

    var forItemWithMods = function(block, elem) {
            var item = elem || block,
                type = elem? 'elem' : 'block',
                args = elem? [block.name, elem.name] : [block.name];

            // for block and element
            cb(type, args, item);

            // for each modifier
            item.mods && item.mods.forEach(function(mod) {

                // for modifier
                cb(type + '-mod', args.concat(mod.name), mod);

                // for each modifier value
                mod.vals && mod.vals.forEach(function(val, i) {
                    if (!val.name) {
                        val = { name: val };
                        mod.vals[i] = val;
                    }
                    cb(type + '-mod-val', args.concat(mod.name, val.name), val);
                });

            });
        },
        forBlockDecl = function(block) {
            // for block
            forItemWithMods(block);

            // for each block element
            block.elems && block.elems.forEach(function(elem) {
                forItemWithMods(block, elem);
            });
        },
        forBlocksDecl = function(blocks) {
            // for each block in declaration
            blocks.forEach(forBlockDecl);
        };

    decl.name && forBlockDecl(decl);
    decl.blocks && forBlocksDecl(decl.blocks);

};

/**
 * Adopted from jquery's extend method. Under the terms of MIT License.
 *
 * http://code.jquery.com/jquery-1.4.2.js
 *
 * Modified by mscdex to use Array.isArray instead of the custom isArray method
 */
var extend = exports.extend = function() {
    // copy reference to target object
    var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy;

    // Handle a deep copy situation
    if (typeof target === 'boolean') {
        deep = target;
        target = arguments[1] || {};
        // skip the boolean and the target
        i = 2;
    }

    // Handle case when target is a string or something (possible in deep copy)
    if (typeof target !== 'object' && !typeof target === 'function')
        target = {};

    var isPlainObject = function(obj) {
        // Must be an Object.
        // Because of IE, we also have to check the presence of the constructor property.
        // Make sure that DOM nodes and window objects don't pass through, as well
        if (!obj || toString.call(obj) !== '[object Object]' || obj.nodeType || obj.setInterval)
            return false;

        var has_own_constructor = hasOwnProperty.call(obj, 'constructor');
        var has_is_property_of_method = hasOwnProperty.call(obj.constructor.prototype, 'isPrototypeOf');
        // Not own constructor property must be Object
        if (obj.constructor && !has_own_constructor && !has_is_property_of_method)
            return false;

        // Own properties are enumerated firstly, so to speed up,
        // if last one is own, then all properties are own.

        var key, last_key;
        for (key in obj)
            last_key = key;

        return typeof last_key === 'undefined' || hasOwnProperty.call(obj, last_key);
    };

    for (; i < length; i++) {
        // Only deal with non-null/undefined values
        if ((options = arguments[i]) !== null) {
            // Extend the base object
            for (name in options) {
                if (!options.hasOwnProperty(name))
                    continue;
                src = target[name];
                copy = options[name];

                // Prevent never-ending loop
                if (target === copy)
                    continue;

                // Recurse if we're merging object literal values or arrays
                if (deep && copy && (isPlainObject(copy) || Array.isArray(copy))) {
                    var clone = src && (isPlainObject(src) || Array.isArray(src)) ? src : Array.isArray(copy) ? [] : {};

                    // Never move original objects, clone them
                    target[name] = extend(deep, clone, copy);

                // Don't bring in undefined values
                } else if (typeof copy !== 'undefined')
                    target[name] = copy;
            }
        }
    }

    // Return the modified object
    return target;
};
<|endoftext|>


<commit_before>/*
 * NTwitBot - util.js
 * Copyright (C) 2016 Jordan Sne
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* eslint no-unused-vars: "off" */

const colors = require('colors');

module.exports = class Util {

    constructor(twitterPkg) {
        this.twitterPkg = twitterPkg;
    }

    /*
     * Logs a normal message to console.
     *   message - String: The message to log.
     */
    log(message) {
        console.log(this.getFormattedTime() + " " + " INFO ".black.bgWhite + " %s", message);
    }

    /*
     * Logs an error message to console.
     *   message - String: The message to log.
     */
    logError(message) {
        console.error(this.getFormattedTime() + " " + " ERROR ".black.bgRed + " %s".red, message);
    }

    /*
     * Logs a debug message to console.
     *   message - String: The message to log.
     */
    logDebug(message) {
        console.error(this.getFormattedTime() + " " + " DEBUG ".black.bgCyan + " %s".cyan, message);
    }

    /*
     * Gets the current date & time to the minute.
     *   Returns - Int: the current time in form: YYYYMMDDhhmm
     */
    getTime() {
        let result = "";
        const date = new Date();

        const year = date.getFullYear();
        result += year;

        const month = date.getMonth() + 1;
        result += (month < 10 ? "0" : "") + month;

        const day = date.getDate();
        result += (day < 10 ? "0" : "") + day;

        const hour = date.getHours();
        result += (hour < 10 ? "0" : "") + hour;

        const minute = date.getMinutes();
        result += (minute < 10 ? "0" : "") + minute;

        return parseInt(result);
    }

    /*
     * Gets the current date & time to the second.
     *   Returns - String: the current time in form: "MM/DD/YYYY hh:mm:ss"
     */
    getFormattedTime() {
        let result = "";
        const date = new Date();

        const month = date.getMonth() + 1;
        result += (month < 10 ? "0" : "") + month + "/";

        const day = date.getDate();
        result += (day < 10 ? "0" : "") + day + "/";

        const year = date.getFullYear();
        result += year + " ";

        const hour = date.getHours();
        result += (hour < 10 ? "0" : "") + hour + ":";

        const minute = date.getMinutes();
        result += (minute < 10 ? "0" : "") + minute + ":";

        const seconds = date.getSeconds();
        result += (seconds < 10 ? "0" : "") + seconds;

        return result;
    }

    /*
     * Capitalizes the first letter of a word.
     *   word    - String: The word to capitalize
     *   Returns - String: The capitalized word
     */
    capitalize(word) {
        return word.charAt(0).toUpperCase() + word.slice(1);
    }

    /*
     * Checks if a character in upper case.
     *   char    - Character: The letter to check.
     *   Returns - Boolean:   true if upper case.
     */
    isUpperCase(char) {
        return char == char.toUpperCase();
    }

    /*
     * Check if an object is in an array.
     *   array   - Array:   Array to check.
     *   object  - Object:  Object to check.
     *   Returns - Boolean: True if object in in the array.
     */
    isInArray(array, object) {
        return array.indexOf(object) !== -1;
    }

    /*
     * Returns the object where the word variable is w.
     *   array   - Array:  Array to check.
     *   w       - String: The word to search for.
     *   Returns - Object: That contains w.
     */
    getObjectWithWord(array, w) {
        for (let i = 0; i < array.length; i++) {
            if (array[i].word === w) {
                return array[i];
            }
        }

        return null;
    }

    /*
     * Check if the end of a string ends with a punctuation symbol.
     *   string  - String:  String to check.
     *   Returns - Boolean: True if the string ends with a punctuation symbol.
     */
    endsWithPunc(string) {
        return string.endsWith(".") || string.endsWith("!") || string.endsWith("?");
    }

};
<commit_msg>Add array to string conversion function<commit_after>/*
 * NTwitBot - util.js
 * Copyright (C) 2016 Jordan Sne
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* eslint no-unused-vars: "off" */

const colors = require('colors');

module.exports = class Util {

    constructor(twitterPkg) {
        this.twitterPkg = twitterPkg;
    }

    /*
     * Logs a normal message to console.
     *   message - String: The message to log.
     */
    log(message) {
        console.log(this.getFormattedTime() + " " + " INFO ".black.bgWhite + " %s", message);
    }

    /*
     * Logs an error message to console.
     *   message - String: The message to log.
     */
    logError(message) {
        console.error(this.getFormattedTime() + " " + " ERROR ".black.bgRed + " %s".red, message);
    }

    /*
     * Logs a debug message to console.
     *   message - String: The message to log.
     */
    logDebug(message) {
        console.error(this.getFormattedTime() + " " + " DEBUG ".black.bgCyan + " %s".cyan, message);
    }

    /*
     * Gets the current date & time to the minute.
     *   Returns - Int: the current time in form: YYYYMMDDhhmm
     */
    getTime() {
        let result = "";
        const date = new Date();

        const year = date.getFullYear();
        result += year;

        const month = date.getMonth() + 1;
        result += (month < 10 ? "0" : "") + month;

        const day = date.getDate();
        result += (day < 10 ? "0" : "") + day;

        const hour = date.getHours();
        result += (hour < 10 ? "0" : "") + hour;

        const minute = date.getMinutes();
        result += (minute < 10 ? "0" : "") + minute;

        return parseInt(result);
    }

    /*
     * Gets the current date & time to the second.
     *   Returns - String: the current time in form: "MM/DD/YYYY hh:mm:ss"
     */
    getFormattedTime() {
        let result = "";
        const date = new Date();

        const month = date.getMonth() + 1;
        result += (month < 10 ? "0" : "") + month + "/";

        const day = date.getDate();
        result += (day < 10 ? "0" : "") + day + "/";

        const year = date.getFullYear();
        result += year + " ";

        const hour = date.getHours();
        result += (hour < 10 ? "0" : "") + hour + ":";

        const minute = date.getMinutes();
        result += (minute < 10 ? "0" : "") + minute + ":";

        const seconds = date.getSeconds();
        result += (seconds < 10 ? "0" : "") + seconds;

        return result;
    }

    /*
     * Capitalizes the first letter of a word.
     *   word    - String: The word to capitalize
     *   Returns - String: The capitalized word
     */
    capitalize(word) {
        return word.charAt(0).toUpperCase() + word.slice(1);
    }

    /*
     * Checks if a character in upper case.
     *   char    - Character: The letter to check.
     *   Returns - Boolean:   true if upper case.
     */
    isUpperCase(char) {
        return char == char.toUpperCase();
    }

    /*
     * Check if an object is in an array.
     *   array   - Array:   Array to check.
     *   object  - Object:  Object to check.
     *   Returns - Boolean: True if object in in the array.
     */
    isInArray(array, object) {
        return array.indexOf(object) !== -1;
    }

    /*
     * Returns the object where the word variable is w.
     *   array   - Array:  Array to check.
     *   w       - String: The word to search for.
     *   Returns - Object: That contains w.
     */
    getObjectWithWord(array, w) {
        for (let i = 0; i < array.length; i++) {
            if (array[i].word === w) {
                return array[i];
            }
        }

        return null;
    }

    /*
     * Compiles an array of strings into an array.
     *   array     - Array:  Array to compile into a string.
     *   seperator - String: The seperator between elements.
     *   Returns   - String: The compiled string.
     */
    compileString(array, seperator) {
        let result = "";

        for (let i = 0; i < array.length; i++) {
            result += array[i];

            if (i < array.length - 1) {
                result += seperator;
            }
        }

        return result;
    }

    /*
     * Check if the end of a string ends with a punctuation symbol.
     *   string  - String:  String to check.
     *   Returns - Boolean: True if the string ends with a punctuation symbol.
     */
    endsWithPunc(string) {
        return string.endsWith(".") || string.endsWith("!") || string.endsWith("?");
    }

};
<|endoftext|>