<commit_before>/*
 * @copyright Copyright (c) Sematext Group, Inc. - All Rights Reserved
 *
 * @licence SPM for NodeJS is free-to-use, proprietary software.
 * THIS IS PROPRIETARY SOURCE CODE OF Sematext Group, Inc. (Sematext)
 * This source code may not be copied, reverse engineered, or altered for any purpose.
 * This source code is to be used exclusively by users and customers of Sematext.
 * Please see the full license (found in LICENSE in this distribution) for details on its license and the licenses of its dependencies.
 */

'use strict'
var moment = require('moment')
var logger = require('./util/logger.js')

function formatArray (a) {
  if (a instanceof Array) {
    return a.join('\t')
  } else {
    return a + ''
  }
}

/**
 * Agent super class that provides an EventEmitter for 'metric' event.
 * In addition it fires for each added metric an event named by the property metric.name
 *
 * @param {function} plugin -  Function providing a plugin having start(agent) and stop method. The plugin can use agent.addMetric function.
 * @returns {function} an Agent having start,stop, addMetric function
 */
function Agent (plugin) {
  var events = require('events')
  var util = require('util')
  var eventEmitter = new events.EventEmitter()
  var cluster = require('cluster')
  var workerId = 0 + '-' + process.pid // 0 == Master, default
  var config = require('./util/spmconfig.js')
  if (!cluster.isMaster) {
    workerId = cluster.worker.id + '-' + process.pid
  }
  var agentSuperClass = {
    plugin: plugin,
    defaultType: 'njs',
    defaultFilters: function () {
      return [workerId || 0, process.pid || 0]
    },
    metricBuffer: [],
    /**
     * Adds a metric value and emits event to listeners on 'metric' or metric.name
     * @param metric
     */
    addMetrics: function (metric) {
      if (metric.measurement && metric.fields) {
        return this.addInfluxMetrics(metric)
      }
      metric.workerId = workerId
      metric.pid = process.pid
      if (!metric.sct) {
        if (metric.name && /collectd/.test(metric.name)) {
          metric.sct = 'OS'
        } else {
          metric.sct = 'APP'
        }
      }
      if (!metric.ts) {
        metric.ts = new Date().getTime()
      }
      if (!metric.type) {
        metric.type = this.defaultType
      }
      if (!metric.filters && metric.type === 'njs') {
        metric.filters = this.defaultFilters()
      }
      metric.spmLine = this.formatLine(metric)
      this.emit('metric', metric)
      if (metric.name) {
        this.emit(metric.name, metric)
      }
    },

    addInfluxMetrics: function (metric) {
      if (!metric) {
        return
      }

      if (!metric.timestamp) {
        metric.timestamp = new Date()
      }
      // numeric values depend on influx-client precision settings
      // while Date objects are correctly converted by influx-client
      if (typeof metric.timestamp === 'number') {
        metric.timestamp = new Date(metric.timestamp)
      }
      this.addInfluxTags(metric)
      this.emit('metric', metric)
      if (metric.measurement) {
        this.emit(metric.measurement, metric)
      }
    },

    addInfluxTags: function (metric) {
      if (!metric.tags) {
        metric.tags = {}
      }
      // Set pid, ppid tags only for nodejs and process metrics
      if (metric.measurement.startsWith('nodejs') ||
          metric.measurement.startsWith('process')) {
        // don't set default tags, when already provided
        if (!metric.tags['nodejs.worker.id']) {
          metric.tags['nodejs.worker.id'] = workerId
        }
        if (!metric.tags['pid']) {
          metric.tags['pid'] = process.pid
        }
        if (!metric.tags['ppid']) {
          metric.tags['ppid'] = process.ppid
        }
      }
      // save the metric token tag, in case global tags set 'token'
      let token = metric.tags.token
      // add global tags to metrics Object
      Object.assign(metric.tags, this.globalTags || {})
      // set metric specific token, e.g. in case of infra-app token
      if (token) {
        metric.tags.token = token
      }
    },

    collectdFormatLine: function (metric) {
      var now = (new Date().getTime()).toFixed(0)
      var metricsTs = (metric.ts).toFixed(0)
      var metricsTs2 = (metric.ts / 1000).toFixed(0)
      var dateString = moment(metric.ts).format('YYYY-MM-DD')
      var line = ''
      if ((/collectd.*-/.test(metric.name))) {
        if (/collectd-cpu/.test(metric.name) || /collectd-io-octets/.test(metric.name)) {
          line = (now + '\t' + metric.name + '-' + dateString + ',' + metricsTs2 + ',' + metric.value)
        } else if (/disk/.test(metric.name)) {
          line = (now + '\t' + metric.name + '-' + dateString + ',' + metricsTs2 + ',' + metric.value.toFixed(6))
        } else {
          line = now + '\t' + metric.name + '\t' + metricsTs2 + ',' + metric.value
        }
      } else {
        line = now + '\t' + metric.name + '\t' + metricsTs + ',' + metric.value
      }
      return line
    },

    defaultFormatLine: function (metric) {
      var now = (new Date().getTime()).toFixed(0)
      var line = null
      if (metric.sct === 'OS') {
        line = this.collectdFormatLine(metric)
      } else {
        line = util.format('%d\t%s\t%d\t%s\t%s', now, (metric.type || this.defaultType) + '-' + metric.name, metric.ts, formatArray(metric.filters), formatArray(metric.value))
        logger.log(line)
      }
      return line
    },
    /**
     * formats a line for SPM sender, if the plugin does not support this method the default formatter for nodejs and OP metrics is used
     * @maram metric to format
     */
    formatLine: function (metric) {
      if (!this.plugin.formatLine) {
        return this.defaultFormatLine(metric)
      } else {
        return this.plugin.formatLine(metric)
      }
    },
    generateGlobalTags: function (tagNames, tagObject) {
      if (tagNames) {
        if (typeof tagNames === 'string') {
          // convert comma separated String list to array
          tagNames = tagNames.replace(/\s/g, '').split(',')
        }
        if (tagNames instanceof Array) {
          tagNames.forEach((tagName) => {
            if (!tagName || tagName === '') {
              return
            }
            if (tagName.indexOf(':') > -1) {
              // static tag a=b
              let keyValue = tagName.split(':')
              if (keyValue.length === 2) {
                tagObject[keyValue[0]] = keyValue[1]
              }
            } else {
              tagObject[tagName] = process.env[tagName]
            }
          })
        }
      }
    },

    /**
     * Starts the agent - typically agents create listeners or interval checks for metrics,
     * and use 'addMetrics' to inform listeners
     */
    start: function () {
      if (!this.globalTags) {
        this.globalTags = {}
      }
      let tagNames = (process.env.MONITORING_TAGS_FROM_ENV || config.influx.tagsFromEnv)
      this.generateGlobalTags(tagNames, this.globalTags)
      if (this.plugin && this.plugin.start) {
        // this is the place to register for external events that return metrics
        this.plugin.start(this)
      } else {
        console.log('NOT called start')
      }
    },
    /**
     * Stops the plugin - timers could be pushed to plugins 'timers' Array, if timers exists it tries to clearInterval(timerId)
     * and tries to call the stop() method of the agent-plugin
     */
    stop: function () {
      if (this.plugin.timers) {
        this.plugin.timers.forEach(function (tid) {
          clearInterval(tid)
        })
      }
      if (this.plugin.stop) {
        this.plugin.stop()
      } else {
        logger.warn('Plugin ' + plugin.name + ' has no stop() function')
      }
    }
  }
  return Object.assign(eventEmitter, agentSuperClass)
}

module.exports = Agent
<commit_msg>add process prefix to pid, ppid tags<commit_after>/*
 * @copyright Copyright (c) Sematext Group, Inc. - All Rights Reserved
 *
 * @licence SPM for NodeJS is free-to-use, proprietary software.
 * THIS IS PROPRIETARY SOURCE CODE OF Sematext Group, Inc. (Sematext)
 * This source code may not be copied, reverse engineered, or altered for any purpose.
 * This source code is to be used exclusively by users and customers of Sematext.
 * Please see the full license (found in LICENSE in this distribution) for details on its license and the licenses of its dependencies.
 */

'use strict'
var moment = require('moment')
var logger = require('./util/logger.js')

function formatArray (a) {
  if (a instanceof Array) {
    return a.join('\t')
  } else {
    return a + ''
  }
}

/**
 * Agent super class that provides an EventEmitter for 'metric' event.
 * In addition it fires for each added metric an event named by the property metric.name
 *
 * @param {function} plugin -  Function providing a plugin having start(agent) and stop method. The plugin can use agent.addMetric function.
 * @returns {function} an Agent having start,stop, addMetric function
 */
function Agent (plugin) {
  var events = require('events')
  var util = require('util')
  var eventEmitter = new events.EventEmitter()
  var cluster = require('cluster')
  var workerId = 0 + '-' + process.pid // 0 == Master, default
  var config = require('./util/spmconfig.js')
  if (!cluster.isMaster) {
    workerId = cluster.worker.id + '-' + process.pid
  }
  var agentSuperClass = {
    plugin: plugin,
    defaultType: 'njs',
    defaultFilters: function () {
      return [workerId || 0, process.pid || 0]
    },
    metricBuffer: [],
    /**
     * Adds a metric value and emits event to listeners on 'metric' or metric.name
     * @param metric
     */
    addMetrics: function (metric) {
      if (metric.measurement && metric.fields) {
        return this.addInfluxMetrics(metric)
      }
      metric.workerId = workerId
      metric.pid = process.pid
      if (!metric.sct) {
        if (metric.name && /collectd/.test(metric.name)) {
          metric.sct = 'OS'
        } else {
          metric.sct = 'APP'
        }
      }
      if (!metric.ts) {
        metric.ts = new Date().getTime()
      }
      if (!metric.type) {
        metric.type = this.defaultType
      }
      if (!metric.filters && metric.type === 'njs') {
        metric.filters = this.defaultFilters()
      }
      metric.spmLine = this.formatLine(metric)
      this.emit('metric', metric)
      if (metric.name) {
        this.emit(metric.name, metric)
      }
    },

    addInfluxMetrics: function (metric) {
      if (!metric) {
        return
      }

      if (!metric.timestamp) {
        metric.timestamp = new Date()
      }
      // numeric values depend on influx-client precision settings
      // while Date objects are correctly converted by influx-client
      if (typeof metric.timestamp === 'number') {
        metric.timestamp = new Date(metric.timestamp)
      }
      this.addInfluxTags(metric)
      this.emit('metric', metric)
      if (metric.measurement) {
        this.emit(metric.measurement, metric)
      }
    },

    addInfluxTags: function (metric) {
      if (!metric.tags) {
        metric.tags = {}
      }
      // Set pid, ppid tags only for nodejs and process metrics
      if (metric.measurement.startsWith('nodejs') ||
          metric.measurement.startsWith('process')) {
        // don't set default tags, when already provided
        if (!metric.tags['nodejs.worker.id']) {
          metric.tags['nodejs.worker.id'] = workerId
        }
        if (!metric.tags['process.pid']) {
          metric.tags['process.pid'] = process.pid
        }
        if (!metric.tags['process.ppid']) {
          metric.tags['process.ppid'] = process.ppid
        }
      }
      // save the metric token tag, in case global tags set 'token'
      let token = metric.tags.token
      // add global tags to metrics Object
      Object.assign(metric.tags, this.globalTags || {})
      // set metric specific token, e.g. in case of infra-app token
      if (token) {
        metric.tags.token = token
      }
    },

    collectdFormatLine: function (metric) {
      var now = (new Date().getTime()).toFixed(0)
      var metricsTs = (metric.ts).toFixed(0)
      var metricsTs2 = (metric.ts / 1000).toFixed(0)
      var dateString = moment(metric.ts).format('YYYY-MM-DD')
      var line = ''
      if ((/collectd.*-/.test(metric.name))) {
        if (/collectd-cpu/.test(metric.name) || /collectd-io-octets/.test(metric.name)) {
          line = (now + '\t' + metric.name + '-' + dateString + ',' + metricsTs2 + ',' + metric.value)
        } else if (/disk/.test(metric.name)) {
          line = (now + '\t' + metric.name + '-' + dateString + ',' + metricsTs2 + ',' + metric.value.toFixed(6))
        } else {
          line = now + '\t' + metric.name + '\t' + metricsTs2 + ',' + metric.value
        }
      } else {
        line = now + '\t' + metric.name + '\t' + metricsTs + ',' + metric.value
      }
      return line
    },

    defaultFormatLine: function (metric) {
      var now = (new Date().getTime()).toFixed(0)
      var line = null
      if (metric.sct === 'OS') {
        line = this.collectdFormatLine(metric)
      } else {
        line = util.format('%d\t%s\t%d\t%s\t%s', now, (metric.type || this.defaultType) + '-' + metric.name, metric.ts, formatArray(metric.filters), formatArray(metric.value))
        logger.log(line)
      }
      return line
    },
    /**
     * formats a line for SPM sender, if the plugin does not support this method the default formatter for nodejs and OP metrics is used
     * @maram metric to format
     */
    formatLine: function (metric) {
      if (!this.plugin.formatLine) {
        return this.defaultFormatLine(metric)
      } else {
        return this.plugin.formatLine(metric)
      }
    },
    generateGlobalTags: function (tagNames, tagObject) {
      if (tagNames) {
        if (typeof tagNames === 'string') {
          // convert comma separated String list to array
          tagNames = tagNames.replace(/\s/g, '').split(',')
        }
        if (tagNames instanceof Array) {
          tagNames.forEach((tagName) => {
            if (!tagName || tagName === '') {
              return
            }
            if (tagName.indexOf(':') > -1) {
              // static tag a=b
              let keyValue = tagName.split(':')
              if (keyValue.length === 2) {
                tagObject[keyValue[0]] = keyValue[1]
              }
            } else {
              tagObject[tagName] = process.env[tagName]
            }
          })
        }
      }
    },

    /**
     * Starts the agent - typically agents create listeners or interval checks for metrics,
     * and use 'addMetrics' to inform listeners
     */
    start: function () {
      if (!this.globalTags) {
        this.globalTags = {}
      }
      let tagNames = (process.env.MONITORING_TAGS_FROM_ENV || config.influx.tagsFromEnv)
      this.generateGlobalTags(tagNames, this.globalTags)
      if (this.plugin && this.plugin.start) {
        // this is the place to register for external events that return metrics
        this.plugin.start(this)
      } else {
        console.log('NOT called start')
      }
    },
    /**
     * Stops the plugin - timers could be pushed to plugins 'timers' Array, if timers exists it tries to clearInterval(timerId)
     * and tries to call the stop() method of the agent-plugin
     */
    stop: function () {
      if (this.plugin.timers) {
        this.plugin.timers.forEach(function (tid) {
          clearInterval(tid)
        })
      }
      if (this.plugin.stop) {
        this.plugin.stop()
      } else {
        logger.warn('Plugin ' + plugin.name + ' has no stop() function')
      }
    }
  }
  return Object.assign(eventEmitter, agentSuperClass)
}

module.exports = Agent
<|endoftext|>


<commit_before>/*
 * Alias Extension
 *
 * Allows a module to be a plain copy of another module by module name
 *
 * System.meta['mybootstrapalias'] = { alias: 'bootstrap' };
 *
 */
(function() {
  // aliases
  hook('fetch', function(fetch) {
    return function(load) {
      var alias = load.metadata.alias;
      var aliasDeps = load.metadata.deps || [];
      if (alias) {
        load.metadata.format = 'defined';
        var entry = createEntry();
        this.defined[load.name] = entry;
        entry.declarative = true;
        entry.deps = aliasDeps.concat([alias]);
        entry.declare = function(_export) {
          return {
            setters: [function(module) {
              for (var p in module)
                _export(p, module[p]);
            }],
            execute: function() {}
          };
        };
        return '';
      }

      return fetch.call(this, load);
    };
  });
})();<commit_msg>alias.js: fix for default export from alias module in cjs format<commit_after>/*
 * Alias Extension
 *
 * Allows a module to be a plain copy of another module by module name
 *
 * System.meta['mybootstrapalias'] = { alias: 'bootstrap' };
 *
 */
(function() {
  // aliases
  hook('fetch', function(fetch) {
    return function(load) {
      var alias = load.metadata.alias;
      var aliasDeps = load.metadata.deps || [];
      if (alias) {
        load.metadata.format = 'defined';
        var entry = createEntry();
        this.defined[load.name] = entry;
        entry.declarative = true;
        entry.deps = aliasDeps.concat([alias]);
        entry.declare = function(_export) {
          return {
            setters: [function(module) {
              for (var p in module)
                _export(p, module[p]);
              if (module.__useDefault)
                entry.module.exports.__useDefault = true;
            }],
            execute: function() {}
          };
        };
        return '';
      }

      return fetch.call(this, load);
    };
  });
})();<|endoftext|>


<commit_before>import retry from 'async-retry';
import dns from 'dns';
import Now from '../lib';
import toHost from './to-host';
import chalk from 'chalk';

export default class Alias extends Now {

  async ls (deployment) {
    if (deployment) {
      const target = await this.findDeployment(deployment);
      if (!target) {
        const err = new Error(`Aliases not found by "${deployment}". Run ${chalk.dim('`now alias ls`')} to see your aliases.`);
        err.userError = true;
        throw err;
      }

      return this.retry(async (bail, attempt) => {
        const res = await this._fetch(`/now/deployments/${target.uid}/aliases`);
        const body = await res.json();
        return body.aliases;
      });
    }

    return this.retry(async (bail, attempt) => {
      const res = await this._fetch('/now/aliases');
      const body = await res.json();
      return body.aliases;
    });
  }

  async rm (alias) {
    const _alias = await this.findAlias(alias);
    if (!_alias) {
      const err = new Error(`Alias not found by "${alias}". Run ${chalk.dim('`now alias ls`')} to see your aliases.`);
      err.userError = true;
      throw err;
    }

    return this.retry(async (bail, attempt) => {
      const res = await this._fetch(`/now/aliases/${_alias.uid}`, {
        method: 'DELETE'
      });

      if (403 === res.status) {
        return bail(new Error('Unauthorized'));
      }

      if (res.status !== 200) {
        const err = new Error('Deletion failed. Try again later.');
        throw err;
      }

      console.log(`${chalk.cyan('> Success!')}`);
    });
  }

  async findAlias (alias) {
    const list = await this.ls();

    let key, val;
    if (/\./.test(alias)) {
      val = toHost(alias);
      key = 'alias';
    } else {
      val = alias;
      key = 'uid';
    }

    const _alias = list.find((d) => {
      if (d[key] === val) {
        if (this._debug) console.log(`> [debug] matched alias ${d.uid} by ${key} ${val}`);
        return true;
      }

      // match prefix
      if (`${val}.now.sh` === d.alias) {
        if (this._debug) console.log(`> [debug] matched alias ${d.uid} by url ${d.host}`);
        return true;
      }

      return false;
    });

    return _alias;
  }

  async findDeployment (deployment) {
    const list = await this.list();
    let key, val;
    if (/\./.test(deployment)) {
      val = toHost(deployment);
      key = 'url';
    } else {
      val = deployment;
      key = 'uid';
    }

    const depl = list.find((d) => {
      if (d[key] === val) {
        if (this._debug) console.log(`> [debug] matched deployment ${d.uid} by ${key} ${val}`);
        return true;
      }

      // match prefix
      if (`${val}.now.sh` === d.url) {
        if (this._debug) console.log(`> [debug] matched deployment ${d.uid} by url ${d.url}`);
        return true;
      }

      return false;
    });

    return depl;
  }

  async set (deployment, alias) {
    const depl = await this.findDeployment(deployment);
    if (!depl) {
      const err = new Error(`Deployment not found by "${deployment}". Run ${chalk.dim('`now ls`')} to see your deployments.`);
      err.userError = true;
      throw err;
    }

    // evaluate the alias
    if (!/\./.test(alias)) {
      if (this._debug) console.log(`> [debug] suffixing \`.now.sh\` to alias ${alias}`);
      alias = `${alias}.now.sh`;
    } else {
      alias = toHost(alias);
    }

    await this.createAlias(depl, alias);
    console.log(`${chalk.cyan('> Success!')} Deployment ${chalk.bold(`https://${depl.url}`)} ${chalk.dim(`(${depl.uid})`)} now points to ${chalk.bold(chalk.underline(`https://${alias}`))}`);
  }

  async createAlias (depl, alias) {
    return this.retry(async (bail, attempt) => {
      if (this._debug) console.time(`> [debug] /now/deployments/${depl.uid}/aliases #${attempt}`);
      const res = await this._fetch(`/now/deployments/${depl.uid}/aliases`, {
        method: 'POST',
        body: { alias }
      });

      if (304 === res.status) return;

      const body = await res.json();
      if (this._debug) console.timeEnd(`> [debug] /now/deployments/${depl.uid}/aliases #${attempt}`);

      // no retry on authorization problems
      if (403 === res.status) {
        const code = body.error.code;

        if ('custom_domain_needs_upgrade' === code) {
          const err = new Error(`You are attempting to use a custom domain alias (${chalk.underline(chalk.cyan(alias))}), but this is only enabled for premium accounts. Please upgrade at ${chalk.underline('https://zeit.co/account')}`);
          err.userError = true;
          return bail(err);
        }

        if ('alias_in_use' === code) {
          const err = new Error(`The alias you are trying to configure (${chalk.underline(chalk.cyan(alias))}) is already in use by a different account.`);
          err.userError = true;
          return bail(err);
        }

        return bail(new Error('Authorization error'));
      }

      // all other errors
      if (body.error) {
        const code = body.error.code;

        if ('deployment_not_found' === code) {
          return bail(new Error('Deployment not found'));
        }

        if ('cert_missing' === code) {
          console.log(`> Provisioning certificate for ${chalk.underline(chalk.cyan(alias))}`);

          await this.verifyOwnership();
          await this.createCert();

          // try again, but now having provisioned the certificate
          return this.createAlias(depl, alias);
        }

        return bail(new Error(body.error.message));
      }

      // the two expected succesful cods are 200 and 304
      if (200 !== res.status && 304 !== res.status) {
        throw new Error('Unhandled error');
      }
    });
  }

  async verifyOwnership (domain) {
    return this.retry(async (bail, attempt) => {
      const targets = await resolve4('alias.zeit.co');

      if (!targets.length) {
        return bail(new Error('Unable to resolve alias.zeit.co'));
      }

      const ips = await resolve4(domain);
      if (!ips.length) {
        const err = new Error('The domain ${domain} A record in the DNS configuration is not returning any IPs.');
        err.userError = true;
        return bail(err);
      }

      for (const ip of ips) {
        if (!~targets.indexOf(ip)) {
          const err = new Error(`The domain ${domain} has an A record ${chalk.bold(ip)} that doesn\'t resolve to ${chalk.bold('alias.zeit.co')}. Make sure the appropriate \`ALIAS\` or \`CNAME\` records are configured.`);
          err.userError = true;
          return bail(err);
        }
      }
    });
  }

  async createCert (domain) {
    return this.retry(async (bail, attempt) => {
      if (this._debug) console.time(`> [debug] /certs #${attempt}`);
      const res = await this._fetch('/certs', {
        method: 'POST',
        body: {
          domains: [domain]
        }
      });

      const body = await res.json();
      if (this._debug) console.timeEnd(`> [debug] /certs #${attempt}`);

      if (body.error) {
        const { code } = body.error;

        if ('verification_failed' === code) {
          const err = new Error(`We couldn't verify ownership of the domain ${domain}. Make sure the appropriate \`ALIAS\` or \`CNAME\` records are configured and pointing to ${chalk.bold('alias.zeit.co')}.`);
          err.userError = true;
          return bail(err);
        }

        throw new Error(body.message);
      }

      if (200 !== res.status && 304 !== res.status) {
        throw new Error('Unhandled error');
      }
    });
  }

  retry (fn) {
    return retry(fn, { retries: 5, randomize: true, onRetry: this._onRetry });
  }

}

function resolve4 (host) {
  return new Promise((resolve, reject) => {
    return dns.resolve4(host, (err, answer) => {
      if (err) return reject(err);
      resolve(answer);
    });
  });
}
<commit_msg>alias: verify ownership first for custom domains<commit_after>import retry from 'async-retry';
import dns from 'dns';
import Now from '../lib';
import toHost from './to-host';
import chalk from 'chalk';

export default class Alias extends Now {

  async ls (deployment) {
    if (deployment) {
      const target = await this.findDeployment(deployment);
      if (!target) {
        const err = new Error(`Aliases not found by "${deployment}". Run ${chalk.dim('`now alias ls`')} to see your aliases.`);
        err.userError = true;
        throw err;
      }

      return this.retry(async (bail, attempt) => {
        const res = await this._fetch(`/now/deployments/${target.uid}/aliases`);
        const body = await res.json();
        return body.aliases;
      });
    }

    return this.retry(async (bail, attempt) => {
      const res = await this._fetch('/now/aliases');
      const body = await res.json();
      return body.aliases;
    });
  }

  async rm (alias) {
    const _alias = await this.findAlias(alias);
    if (!_alias) {
      const err = new Error(`Alias not found by "${alias}". Run ${chalk.dim('`now alias ls`')} to see your aliases.`);
      err.userError = true;
      throw err;
    }

    return this.retry(async (bail, attempt) => {
      const res = await this._fetch(`/now/aliases/${_alias.uid}`, {
        method: 'DELETE'
      });

      if (403 === res.status) {
        return bail(new Error('Unauthorized'));
      }

      if (res.status !== 200) {
        const err = new Error('Deletion failed. Try again later.');
        throw err;
      }

      console.log(`${chalk.cyan('> Success!')}`);
    });
  }

  async findAlias (alias) {
    const list = await this.ls();

    let key, val;
    if (/\./.test(alias)) {
      val = toHost(alias);
      key = 'alias';
    } else {
      val = alias;
      key = 'uid';
    }

    const _alias = list.find((d) => {
      if (d[key] === val) {
        if (this._debug) console.log(`> [debug] matched alias ${d.uid} by ${key} ${val}`);
        return true;
      }

      // match prefix
      if (`${val}.now.sh` === d.alias) {
        if (this._debug) console.log(`> [debug] matched alias ${d.uid} by url ${d.host}`);
        return true;
      }

      return false;
    });

    return _alias;
  }

  async findDeployment (deployment) {
    const list = await this.list();
    let key, val;
    if (/\./.test(deployment)) {
      val = toHost(deployment);
      key = 'url';
    } else {
      val = deployment;
      key = 'uid';
    }

    const depl = list.find((d) => {
      if (d[key] === val) {
        if (this._debug) console.log(`> [debug] matched deployment ${d.uid} by ${key} ${val}`);
        return true;
      }

      // match prefix
      if (`${val}.now.sh` === d.url) {
        if (this._debug) console.log(`> [debug] matched deployment ${d.uid} by url ${d.url}`);
        return true;
      }

      return false;
    });

    return depl;
  }

  async set (deployment, alias) {
    alias = alias.toLowerCase();

    const depl = await this.findDeployment(deployment);
    if (!depl) {
      const err = new Error(`Deployment not found by "${deployment}". Run ${chalk.dim('`now ls`')} to see your deployments.`);
      err.userError = true;
      throw err;
    }

    // evaluate the alias
    if (!/\./.test(alias)) {
      if (this._debug) console.log(`> [debug] suffixing \`.now.sh\` to alias ${alias}`);
      alias = `${alias}.now.sh`;
    } else {
      alias = toHost(alias);
    }

    if (!/\.now\.sh$/.test(alias)) {
      console.log(`> ${chalk.bold(chalk.underline(alias))} is a custom domain.`);
      console.log(`> Verifying that ${chalk.bold(chalk.underline(alias))} has a ${chalk.dim('`CNAME`')} or ${chalk.dim('`ALIAS`')} record pointing to ${chalk.bold(chalk.underline('alias.zeit.co'))}.`);
      await this.verifyOwnership(alias);
    }

    await this.createAlias(depl, alias);
    console.log(`${chalk.cyan('> Success!')} Deployment ${chalk.bold(`https://${depl.url}`)} ${chalk.dim(`(${depl.uid})`)} now points to ${chalk.bold(chalk.underline(`https://${alias}`))}`);
  }

  createAlias (depl, alias) {
    return this.retry(async (bail, attempt) => {
      if (this._debug) console.time(`> [debug] /now/deployments/${depl.uid}/aliases #${attempt}`);
      const res = await this._fetch(`/now/deployments/${depl.uid}/aliases`, {
        method: 'POST',
        body: { alias }
      });

      if (304 === res.status) return;

      const body = await res.json();
      if (this._debug) console.timeEnd(`> [debug] /now/deployments/${depl.uid}/aliases #${attempt}`);

      // no retry on authorization problems
      if (403 === res.status) {
        const code = body.error.code;

        if ('custom_domain_needs_upgrade' === code) {
          const err = new Error(`You are attempting to use a custom domain alias (${chalk.underline(chalk.cyan(alias))}), but this is only enabled for premium accounts. Please upgrade at ${chalk.underline('https://zeit.co/account')}`);
          err.userError = true;
          return bail(err);
        }

        if ('alias_in_use' === code) {
          const err = new Error(`The alias you are trying to configure (${chalk.underline(chalk.cyan(alias))}) is already in use by a different account.`);
          err.userError = true;
          return bail(err);
        }

        return bail(new Error('Authorization error'));
      }

      // all other errors
      if (body.error) {
        const code = body.error.code;

        if ('deployment_not_found' === code) {
          return bail(new Error('Deployment not found'));
        }

        if ('cert_missing' === code) {
          console.log(`> Provisioning certificate for ${chalk.underline(chalk.cyan(alias))}`);

          await this.createCert();

          // try again, but now having provisioned the certificate
          return this.createAlias(depl, alias);
        }

        return bail(new Error(body.error.message));
      }

      // the two expected succesful cods are 200 and 304
      if (200 !== res.status && 304 !== res.status) {
        throw new Error('Unhandled error');
      }
    });
  }

  verifyOwnership (domain) {
    return this.retry(async (bail, attempt) => {
      const targets = await resolve4('alias.zeit.co');

      if (!targets.length) {
        return bail(new Error('Unable to resolve alias.zeit.co'));
      }

      const ips = await resolve4(domain);
      if (!ips.length) {
        const err = new Error('The domain ${domain} A record in the DNS configuration is not returning any IPs.');
        err.userError = true;
        return bail(err);
      }

      for (const ip of ips) {
        if (!~targets.indexOf(ip)) {
          const err = new Error(`The domain ${domain} has an A record ${chalk.bold(ip)} that doesn\'t resolve to ${chalk.bold(chalk.underline('alias.zeit.co'))}. Please check your DNS settings.`);
          err.userError = true;
          return bail(err);
        }
      }
    });
  }

  createCert (domain) {
    return this.retry(async (bail, attempt) => {
      if (this._debug) console.time(`> [debug] /certs #${attempt}`);
      const res = await this._fetch('/certs', {
        method: 'POST',
        body: {
          domains: [domain]
        }
      });

      const body = await res.json();
      if (this._debug) console.timeEnd(`> [debug] /certs #${attempt}`);

      if (body.error) {
        const { code } = body.error;

        if ('verification_failed' === code) {
          const err = new Error(`We couldn't verify ownership of the domain ${domain}. Make sure the appropriate \`ALIAS\` or \`CNAME\` records are configured and pointing to ${chalk.bold('alias.zeit.co')}.`);
          err.userError = true;
          return bail(err);
        }

        throw new Error(body.message);
      }

      if (200 !== res.status && 304 !== res.status) {
        throw new Error('Unhandled error');
      }
    });
  }

  retry (fn) {
    return retry(fn, { retries: 5, randomize: true, onRetry: this._onRetry });
  }

}

function resolve4 (host) {
  return new Promise((resolve, reject) => {
    return dns.resolve4(host, (err, answer) => {
      if (err) return reject(err);
      resolve(answer);
    });
  });
}
<|endoftext|>


<commit_before>(function (JSUS) {

	function ARRAY(){};

	// ARRAYs
	/////////

	// Add the filter method to ARRAY objects in case the method is not
	// supported natively.
	// See https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/ARRAY/filter#Compatibility

    if (!Array.prototype.filter) {
      Array.prototype.filter = function(fun /*, thisp */) {
        "use strict";
        if (this === void 0 || this === null) throw new TypeError();

        var t = Object(this);
        var len = t.length >>> 0;
        if (typeof fun !== "function") throw new TypeError();

        var res = [];
        var thisp = arguments[1];
        for (var i = 0; i < len; i++) {
          if (i in t) {
            var val = t[i]; // in case fun mutates this
            if (fun.call(thisp, val, i, t)) {
              res.push(val);
            }
          }
        }

        return res;
      };
    }

    ARRAY.removeElement = function (needle, haystack) {

//    	console.log('needle');
//    	console.log(needle);
//    	console.log('haystack');
//    	console.log(haystack);

    	if ('object' === typeof needle) {
        	  var func = JSUS.equals;
	      }
	      else {
	    	  var func = function (a,b) {
	    		  return (a === b);
	    	  }
	      }

    	for (var i=0; i < haystack.length; i++) {
      	    if (func(needle, haystack[i])){
//      	    	console.log('hays');
//      	    	console.log(haystack);
      	    	return haystack.splice(i,1);
      	    }
      	}

      	return false;

    };

    ARRAY.in_array = function (needle, haystack){

        if ('object' === typeof needle) {
      	  var func = JSUS.equals;
        }
        else {
      	  var func = function (a,b) {
      		  return (a === b);
      	  }
        }

  	  for(var i=0;i<haystack.length;i++){
  	    if (func.call(this,needle,haystack[i])) return true;
  	  }
  	  return false;
    };

    /**
	 * Returns an array of N array containing the same number of elements
	 * The last group could have less elements.
	 *
	 */
	ARRAY.getNGroups = function (array, N) {
		return ARRAY.getGroupsSizeN(array, Math.floor(array.length / N));
	};

	/**
	 * Returns an array of array containing N elements each
	 * The last group could have less elements.
	 *
	 */
	ARRAY.getGroupsSizeN = function (array, N) {

		var copy = array.slice(0);
		var len = copy.length;
		var originalLen = copy.length;
		var result = [];

		// Init values for the loop algorithm
		var i;
		var idx;
		var group = [];
		var count = 0;
		for (i=0; i < originalLen; i++) {

			// Get a random idx between 0 and array length
			idx = Math.floor(Math.random()*len);

			// Prepare the array container for the elements of a new group
			if (count >= N) {
				result.push(group);
				count = 0;
				group = [];
			}

			// Insert element in the group
			group.push(copy[idx]);

			// Update
			copy.splice(idx,1);
			len = copy.length;
			count++;
		}

		// Add any remaining element
		if (group.length > 0) {
			result.push(group);
		}

		return result;
	};

	/**
	 * Match each element of the array with N random others.
	 * If strict is equal to true, elements cannot be matched multiple times.
	 *
	 * TODO: This has a bug / feature. The last element could remain alone,
	 * because all the other have been already coupled. Another recombination
	 * would be able to match all the elements instead.
	 */
	ARRAY.matchN = function (array, N, strict) {
//		console.log('TO MATCH');
//		console.log(array.length);
//
		var result = []
		var len = array.length;
		var found = [];
		for (var i = 0 ; i < len ; i++) {
			// Recreate the array
			var copy = array.slice(0);
			copy.splice(i,1);
			if (strict) {
				copy = ARRAY.arrayDiff(copy,found);
			}
			var group = ARRAY.getNRandom(copy,N);
			// Add to the set of used elements
			found = found.concat(group);
			// Re-add the current element
			group.splice(0,0,array[i]);
			result.push(group);

			//Update
			group = [];

		}
		return result;
	};

	ARRAY.arraySelfConcat = function(array) {
		var i = 0;
		var len = array.length;
		var result = []
		for (;i<len;i++) {
			result = result.concat(array[i]);
		}
		return result;
	};

	/**
	 * Compute the intersection between two arrays.
	 * Arrays can contain both primitive types and objects.
	 */
	ARRAY.arrayIntersect = function (a1, a2) {
		return a1.filter( function(i) {
			return JSUS.in_array(i, a2);
		});
	};

	/**
	 * Perform a diff between two arrays.
	 * Arrays can contain both primitive types and objects.
	 * Returns all the values of the first array which are not present
	 * in the second one.
	 */
	ARRAY.arrayDiff = function (a1, a2) {
		return a1.filter( function(i) {
			return !(JSUS.in_array(i, a2));
		});
	};

	/**
	 * http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
	 *
	 */
	ARRAY.shuffle = function (array) {
		var copy = array.slice(0);
		var len = array.length-1; // ! -1
		for (var i = len; i > 0; i--) {
			var j = Math.floor(Math.random()*(i+1));
			var tmp = copy[j];
			copy[j] = copy[i];
			copy[i] = tmp;
			//console.log(copy);
		}
		return copy;
	};

	ARRAY.getNRandom = function (array, N, callback) {
		return ARRAY.shuffle(array).slice(0,N);
	};

	ARRAY.generateCombinations = function (array, r, callback) {
	    function equal(a, b) {
	        for (var i = 0; i < a.length; i++) {
	            if (a[i] != b[i]) return false;
	        }
	        return true;
	    }
	    function values(i, a) {
	        var ret = [];
	        for (var j = 0; j < i.length; j++) ret.push(a[i[j]]);
	        return ret;
	    }
	    var n = array.length;
	    var indices = [];
	    for (var i = 0; i < r; i++) indices.push(i);
	    var final = [];
	    for (var i = n - r; i < n; i++) final.push(i);
	    while (!equal(indices, final)) {
	        callback(values(indices, array));
	        var i = r - 1;
	        while (indices[i] == n - r + i) i -= 1;
	        indices[i] += 1;
	        for (var j = i + 1; j < r; j++) indices[j] = indices[i] + j - i;
	    }
	    callback(values(indices, array));
	};

	//TODO: Improve
	ARRAY.print_r = function (array) {
		for (var i=0,len=array.length;i<len;i++){
			var el = array[i];
			if (typeof(el) === 'ARRAY'){
				ARRAY.print_r(el);
			}
			else {
				if (typeof(el) === 'Object'){
					for (var key in el) {
						if (el.hasOwnProperty(key)){
							console.log(key + ' ->\n');
							ARRAY.print_r(el[key]);
						}
					}
				}
				else {
					console.log(el);
				}
			}
		}
	};

	JSUS.extend(ARRAY);

})('undefined' !== typeof JSUS ? JSUS : module.parent.exports.JSUS);<commit_msg>On the process of creating DynamicTable. Takes time...<commit_after>(function (JSUS) {

	function ARRAY(){};

	// ARRAYs
	/////////

	// Add the filter method to ARRAY objects in case the method is not
	// supported natively.
	// See https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/ARRAY/filter#Compatibility

    if (!Array.prototype.filter) {
      Array.prototype.filter = function(fun /*, thisp */) {
        "use strict";
        if (this === void 0 || this === null) throw new TypeError();

        var t = Object(this);
        var len = t.length >>> 0;
        if (typeof fun !== "function") throw new TypeError();

        var res = [];
        var thisp = arguments[1];
        for (var i = 0; i < len; i++) {
          if (i in t) {
            var val = t[i]; // in case fun mutates this
            if (fun.call(thisp, val, i, t)) {
              res.push(val);
            }
          }
        }

        return res;
      };
    }

    ARRAY.removeElement = function (needle, haystack) {

//    	console.log('needle');
//    	console.log(needle);
//    	console.log('haystack');
//    	console.log(haystack);

    	if ('object' === typeof needle) {
        	  var func = JSUS.equals;
	      }
	      else {
	    	  var func = function (a,b) {
	    		  return (a === b);
	    	  }
	      }

    	for (var i=0; i < haystack.length; i++) {
      	    if (func(needle, haystack[i])){
//      	    	console.log('hays');
//      	    	console.log(haystack);
      	    	return haystack.splice(i,1);
      	    }
      	}

      	return false;

    };

    ARRAY.in_array = function (needle, haystack) {
  	  	if ('undefined' === typeof needle || !haystack) return false;

        if ('object' === typeof needle) {
      	  var func = JSUS.equals;
        }
        else {
      	  var func = function (a,b) {
      		  return (a === b);
      	  }
        }

  	  for(var i=0;i<haystack.length;i++){
  	    if (func.call(this,needle,haystack[i])) return true;
  	  }
  	  return false;
    };

    /**
	 * Returns an array of N array containing the same number of elements
	 * The last group could have less elements.
	 *
	 */
	ARRAY.getNGroups = function (array, N) {
		return ARRAY.getGroupsSizeN(array, Math.floor(array.length / N));
	};

	/**
	 * Returns an array of array containing N elements each
	 * The last group could have less elements.
	 *
	 */
	ARRAY.getGroupsSizeN = function (array, N) {

		var copy = array.slice(0);
		var len = copy.length;
		var originalLen = copy.length;
		var result = [];

		// Init values for the loop algorithm
		var i;
		var idx;
		var group = [];
		var count = 0;
		for (i=0; i < originalLen; i++) {

			// Get a random idx between 0 and array length
			idx = Math.floor(Math.random()*len);

			// Prepare the array container for the elements of a new group
			if (count >= N) {
				result.push(group);
				count = 0;
				group = [];
			}

			// Insert element in the group
			group.push(copy[idx]);

			// Update
			copy.splice(idx,1);
			len = copy.length;
			count++;
		}

		// Add any remaining element
		if (group.length > 0) {
			result.push(group);
		}

		return result;
	};

	/**
	 * Match each element of the array with N random others.
	 * If strict is equal to true, elements cannot be matched multiple times.
	 *
	 * TODO: This has a bug / feature. The last element could remain alone,
	 * because all the other have been already coupled. Another recombination
	 * would be able to match all the elements instead.
	 */
	ARRAY.matchN = function (array, N, strict) {
//		console.log('TO MATCH');
//		console.log(array.length);
//
		var result = []
		var len = array.length;
		var found = [];
		for (var i = 0 ; i < len ; i++) {
			// Recreate the array
			var copy = array.slice(0);
			copy.splice(i,1);
			if (strict) {
				copy = ARRAY.arrayDiff(copy,found);
			}
			var group = ARRAY.getNRandom(copy,N);
			// Add to the set of used elements
			found = found.concat(group);
			// Re-add the current element
			group.splice(0,0,array[i]);
			result.push(group);

			//Update
			group = [];

		}
		return result;
	};

	ARRAY.arraySelfConcat = function(array) {
		var i = 0;
		var len = array.length;
		var result = []
		for (;i<len;i++) {
			result = result.concat(array[i]);
		}
		return result;
	};

	/**
	 * Compute the intersection between two arrays.
	 * Arrays can contain both primitive types and objects.
	 */
	ARRAY.arrayIntersect = function (a1, a2) {
		return a1.filter( function(i) {
			return JSUS.in_array(i, a2);
		});
	};

	/**
	 * Perform a diff between two arrays.
	 * Arrays can contain both primitive types and objects.
	 * Returns all the values of the first array which are not present
	 * in the second one.
	 */
	ARRAY.arrayDiff = function (a1, a2) {
		return a1.filter( function(i) {
			return !(JSUS.in_array(i, a2));
		});
	};

	/**
	 * http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
	 *
	 */
	ARRAY.shuffle = function (array) {
		var copy = array.slice(0);
		var len = array.length-1; // ! -1
		for (var i = len; i > 0; i--) {
			var j = Math.floor(Math.random()*(i+1));
			var tmp = copy[j];
			copy[j] = copy[i];
			copy[i] = tmp;
			//console.log(copy);
		}
		return copy;
	};

	ARRAY.getNRandom = function (array, N, callback) {
		return ARRAY.shuffle(array).slice(0,N);
	};

	ARRAY.generateCombinations = function (array, r, callback) {
	    function equal(a, b) {
	        for (var i = 0; i < a.length; i++) {
	            if (a[i] != b[i]) return false;
	        }
	        return true;
	    }
	    function values(i, a) {
	        var ret = [];
	        for (var j = 0; j < i.length; j++) ret.push(a[i[j]]);
	        return ret;
	    }
	    var n = array.length;
	    var indices = [];
	    for (var i = 0; i < r; i++) indices.push(i);
	    var final = [];
	    for (var i = n - r; i < n; i++) final.push(i);
	    while (!equal(indices, final)) {
	        callback(values(indices, array));
	        var i = r - 1;
	        while (indices[i] == n - r + i) i -= 1;
	        indices[i] += 1;
	        for (var j = i + 1; j < r; j++) indices[j] = indices[i] + j - i;
	    }
	    callback(values(indices, array));
	};

	//TODO: Improve
	ARRAY.print_r = function (array) {
		for (var i=0,len=array.length;i<len;i++){
			var el = array[i];
			if (typeof(el) === 'ARRAY'){
				ARRAY.print_r(el);
			}
			else {
				if (typeof(el) === 'Object'){
					for (var key in el) {
						if (el.hasOwnProperty(key)){
							console.log(key + ' ->\n');
							ARRAY.print_r(el[key]);
						}
					}
				}
				else {
					console.log(el);
				}
			}
		}
	};

	JSUS.extend(ARRAY);

})('undefined' !== typeof JSUS ? JSUS : module.parent.exports.JSUS);<|endoftext|>


<commit_before>(function(){

  var
      watch   = require( 'watch-tree' ),
      async   = require( 'asyncjs' ),
      fs      = require( 'fs' ),
      util    = require( 'util' ),
      path    = require( 'path' ),
      inspect = util.inspect,
      plugins   = require('./plugins');

  module.exports.plugins  = plugins;
  module.exports.Compiler = Compiler;

  function Compiler( rootDir, outputDir, watchMode ) {
    this.rootDir   = rootDir;
    this.outputDir = outputDir;
    this.watchMode = !!watchMode;
    this.rules     = [];
    this.ignores   = [];
    return this;
  }

  Compiler.prototype.start = function() {
    var that      = this,
        watchMode = that.watchMode,
        rootDir   = that.rootDir,
        outputDir = that.outputDir;

    var watcher = watch.watchTree(rootDir, {
      'sample-rate' : 5,
      'ignore'      : new RegExp('/[._]')
    });

    var _compile = function(path){ return that._compile(path); };
    var _delete  = function(path){ return that._delete(path);  };

    watcher.on( 'filePreexisted', _compile );
    watcher.on( 'fileCreated',    _compile );
    watcher.on( 'fileModified',   _compile );
    watcher.on( 'fileDeleted',    _delete  );
    watcher.on( 'allPreexistingFilesReported', function(){
      if (watchMode) {
        console.log("-----> Entering watch mode!");
      } else {
        console.log("-----> Done!");
        watcher.end();
      }
    });
  }

  Compiler.prototype.addRule = function( test, outputFilenameFunction, ruleList ) {
    this.rules.push({
      test:test,
      outputFilenameFunction:outputFilenameFunction,
      ruleList:ruleList
    });
  }

  Compiler.prototype.copy = function( test ) {
    this.rules.push({
      test: test,
      outputFilenameFunction: function(type, filePath){ return type === 'original' ? filePath : null; },
      ruleList: [plugins.read, plugins.save]
    });
  }

  Compiler.prototype.ignore = function( test ) {
    this.ignores.push( test );
  }

  function checkTest( test, target ) {
    if (test instanceof RegExp && test.test(target) ) {
      return true;
    }
    else if (typeof test === 'function' && test(target) === true) {
      return true;
    }
    else if (typeof test === 'string' && test === target) {
      return true;
    }
  }

  Compiler.prototype._compile = function(path) {
    path = path.replace(this.rootDir + "/", "");

    for (var i = 0, len = this.ignores.length; i < len; i++) {
      if (checkTest( this.ignores[i], path )) {
        return;
      }
    }

    for (var i = 0, len = this.rules.length; i < len; i++) {
      var rule = this.rules[i];
      if (checkTest( rule.test, path )) {
        this._runChain( path, rule, rule.ruleList );
        return;
      }
    }

    // should probably bark that no rule covered this?
  }

  Compiler.prototype._delete = function(path) {
  }

  Compiler.prototype._runChain = function(file, rule, functions) {
    async.list(functions).call({compiler:this,rule:rule,path:file,save:{}}).end(function(e){
      if (e) {
        console.log("---\n---\n---\n\nERROR IN FILE: "+file+"\n\nError: "+e+"\n\n---\n---\n---\n");
      } else {
//        console.log(" COMPILED: " + file);
      }
    });
  }

})();<commit_msg>all my whitespace must be awesome. at all times. forever.<commit_after>(function(){

  var
      watch   = require( 'watch-tree' ),
      async   = require( 'asyncjs' ),
      fs      = require( 'fs' ),
      util    = require( 'util' ),
      path    = require( 'path' ),
      inspect = util.inspect,
      plugins = require('./plugins');

  module.exports.plugins  = plugins;
  module.exports.Compiler = Compiler;

  function Compiler( rootDir, outputDir, watchMode ) {
    this.rootDir   = rootDir;
    this.outputDir = outputDir;
    this.watchMode = !!watchMode;
    this.rules     = [];
    this.ignores   = [];
    return this;
  }

  Compiler.prototype.start = function() {
    var that      = this,
        watchMode = that.watchMode,
        rootDir   = that.rootDir,
        outputDir = that.outputDir;

    var watcher = watch.watchTree(rootDir, {
      'sample-rate' : 5,
      'ignore'      : new RegExp('/[._]')
    });

    var _compile = function(path){ return that._compile(path); };
    var _delete  = function(path){ return that._delete(path);  };

    watcher.on( 'filePreexisted', _compile );
    watcher.on( 'fileCreated',    _compile );
    watcher.on( 'fileModified',   _compile );
    watcher.on( 'fileDeleted',    _delete  );
    watcher.on( 'allPreexistingFilesReported', function(){
      if (watchMode) {
        console.log("-----> Entering watch mode!");
      } else {
        console.log("-----> Done!");
        watcher.end();
      }
    });
  }

  Compiler.prototype.addRule = function( test, outputFilenameFunction, ruleList ) {
    this.rules.push({
      test:test,
      outputFilenameFunction:outputFilenameFunction,
      ruleList:ruleList
    });
  }

  Compiler.prototype.copy = function( test ) {
    this.rules.push({
      test: test,
      outputFilenameFunction: function(type, filePath){ return type === 'original' ? filePath : null; },
      ruleList: [plugins.read, plugins.save]
    });
  }

  Compiler.prototype.ignore = function( test ) {
    this.ignores.push( test );
  }

  function checkTest( test, target ) {
    if (test instanceof RegExp && test.test(target) ) {
      return true;
    }
    else if (typeof test === 'function' && test(target) === true) {
      return true;
    }
    else if (typeof test === 'string' && test === target) {
      return true;
    }
  }

  Compiler.prototype._compile = function(path) {
    path = path.replace(this.rootDir + "/", "");

    for (var i = 0, len = this.ignores.length; i < len; i++) {
      if (checkTest( this.ignores[i], path )) {
        return;
      }
    }

    for (var i = 0, len = this.rules.length; i < len; i++) {
      var rule = this.rules[i];
      if (checkTest( rule.test, path )) {
        this._runChain( path, rule, rule.ruleList );
        return;
      }
    }

    // should probably bark that no rule covered this?
  }

  Compiler.prototype._delete = function(path) {
  }

  Compiler.prototype._runChain = function(file, rule, functions) {
    async.list(functions).call({compiler:this,rule:rule,path:file,save:{}}).end(function(e){
      if (e) {
        console.log("---\n---\n---\n\nERROR IN FILE: "+file+"\n\nError: "+e+"\n\n---\n---\n---\n");
      } else {
//        console.log(" COMPILED: " + file);
      }
    });
  }

})();<|endoftext|>


<commit_before>const Canvas = require('./canvas');

var Ball = function(game, paddle, bricks){
  var canvas = new Canvas().canvas;
  this.game = game;
  this.paddle = paddle;
  this.startAngle = 0;
  this.x = canvas.width/2;
  this.y = canvas.height-30;
  this.canvasHeightOffset = 8;
  this.radius = 10;
  this.gameSize = {x: this.game.size.x, y: this.game.size.y};
  this.bricks = bricks;
  this.dx = 0
  this.dy = 7
}

Ball.prototype = {

  draw: function(canvas) {
    canvas.beginPath();
    canvas.arc(this.x, this.y, this.radius, this.startAngle, Math.PI*2);
    canvas.fillStyle = "#F00000";
    canvas.fill();
    canvas.closePath();
  },

  reset: function(){
    this.startAngle = 0;
    this.x = canvas.width/2;
    this.y = canvas.height-30;
    this.dy = -this.dy
  },

  setLeftSlope: function(){
  this.dx = -9
  this.dy = -this.dy
  },

  setLeftCenterSlope: function(){
  this.dx = -8
  this.dy = -this.dy
  },

  setCenterSlope: function(){
  this.dx = 0
  this.dy = -this.dy
  },

  setRightCenterSlope: function(){
  this.dx = 8
  this.dy = -this.dy
  },

  setRightSlope: function(){
  this.dx = 9
  this.dy = -this.dy
  },

  setPaddleSound: function(){
    this.game.paddleSound.load();
    this.game.paddleSound.play();
  },

  setWallSound: function(){
    this.game.wallSound.load();
    this.game.wallSound.play();
  },

  setTopSound: function(){
    this.game.topSound.load();
    this.game.topSound.play();
  },

  move: function () {
    this.collisionDetectionBricks();

    if(this.x + this.dx > this.gameSize.x-this.radius || this.x + this.dx < this.radius) {
      this.setWallSound();
      this.dx = -this.dx;
    }

    if((this.y - this.canvasHeightOffset) + this.dy < this.radius) {
      this.setTopSound();
      this.dy = -this.dy;
    }

    //Drops Below Padde
    else if(this.y + this.dy > this.gameSize.y - (this.radius * 2)) {

      //Left Hit - between -4 and 29 pixels
      if(this.x > this.paddle.position.x - 4 && this.x < this.paddle.position.x + 29) {

        this.setPaddleSound();
        this.setLeftSlope();

      // Left Center Hit - between 30 and 59 pixels
      } else if ((this.x > this.paddle.position.x - 4) && (this.x > this.paddle.position.x + 30) && (this.x < this.paddle.position.x + 59)) {

        this.setPaddleSound();
        this.setLeftCenterSlope();

      // Center Hit - between 60 and 89 pixels
      } else if ((this.x > this.paddle.position.x - 4) && (this.x > this.paddle.position.x + 60) && (this.x < this.paddle.position.x + 89)) {

        this.setPaddleSound();
        this.setCenterSlope();

      // Right Center Hit - between 90 and 119 pixels
      } else if ((this.x > this.paddle.position.x - 4) && (this.x > this.paddle.position.x + 90) && (this.x < this.paddle.position.x + 119)) {

        this.setPaddleSound();
        this.setRightCenterSlope();

      // Right Hit - between 120 and 154 pixels
      } else if ((this.x > this.paddle.position.x - 4) && (this.x > this.paddle.position.x + 120) && (this.x < this.paddle.position.x + 154)) {

        this.setPaddleSound();
        this.setRightSlope();

      } else {
        this.game.lives -= 1;
        if (this.game.lives > 0){
          this.reset();
        } else {
          alert("You Fell into the Wall - GAME OVER");
          this.game.deathSound.load();
          this.game.deathSound.play();
          document.location.reload();
        }
      }
   }
    this.x += this.dx;
    this.y += this.dy;
  },

  collisionDetectionBricks: function() {
    var self = this;
    var brickHeight = self.bricks[0].size.y;
    var brickWidth = self.bricks[0].size.x;

    for (var i = 0; i < self.bricks.length; i++) {
      if((self.x > self.bricks[i].position.x)
        && (self.x < self.bricks[i].position.x + brickWidth)
        && (self.y - self.radius < self.bricks[i].position.y + brickHeight)
        && (self.y + self.radius > self.bricks[i].position.y - brickHeight)) {
        this.game.brickSound.load();
        this.game.brickSound.play();
        this.dy = -this.dy;
        self.bricks[i].status = 0;
        self.bricks.splice(i, 1);
        self.game.score += 10;
      }
    }
    return this.dy;
  }
};

module.exports = Ball;<commit_msg>Refactored death sound<commit_after>const Canvas = require('./canvas');

var Ball = function(game, paddle, bricks){
  var canvas = new Canvas().canvas;
  this.game = game;
  this.paddle = paddle;
  this.startAngle = 0;
  this.x = canvas.width/2;
  this.y = canvas.height-30;
  this.canvasHeightOffset = 8;
  this.radius = 10;
  this.gameSize = {x: this.game.size.x, y: this.game.size.y};
  this.bricks = bricks;
  this.dx = 0
  this.dy = 7
}

Ball.prototype = {

  draw: function(canvas) {
    canvas.beginPath();
    canvas.arc(this.x, this.y, this.radius, this.startAngle, Math.PI*2);
    canvas.fillStyle = "#F00000";
    canvas.fill();
    canvas.closePath();
  },

  reset: function(){
    this.startAngle = 0;
    this.x = canvas.width/2;
    this.y = canvas.height-30;
    this.dy = -this.dy
  },

  setLeftSlope: function(){
  this.dx = -9
  this.dy = -this.dy
  },

  setLeftCenterSlope: function(){
  this.dx = -8
  this.dy = -this.dy
  },

  setCenterSlope: function(){
  this.dx = 0
  this.dy = -this.dy
  },

  setRightCenterSlope: function(){
  this.dx = 8
  this.dy = -this.dy
  },

  setRightSlope: function(){
  this.dx = 9
  this.dy = -this.dy
  },

  setPaddleSound: function(){
    this.game.paddleSound.load();
    this.game.paddleSound.play();
  },

  setWallSound: function(){
    this.game.wallSound.load();
    this.game.wallSound.play();
  },

  setTopSound: function(){
    this.game.topSound.load();
    this.game.topSound.play();
  },

  setDeathSound: function(){
    this.game.deathSound.load();
    this.game.deathSound.play();
  },

  move: function () {
    this.collisionDetectionBricks();

    if(this.x + this.dx > this.gameSize.x-this.radius || this.x + this.dx < this.radius) {
      this.setWallSound();
      this.dx = -this.dx;
    }

    if((this.y - this.canvasHeightOffset) + this.dy < this.radius) {
      this.setTopSound();
      this.dy = -this.dy;
    }

    //Drops Below Padde
    else if(this.y + this.dy > this.gameSize.y - (this.radius * 2)) {

      //Left Hit - between -4 and 29 pixels
      if(this.x > this.paddle.position.x - 4 && this.x < this.paddle.position.x + 29) {

        this.setPaddleSound();
        this.setLeftSlope();

      // Left Center Hit - between 30 and 59 pixels
      } else if ((this.x > this.paddle.position.x - 4) && (this.x > this.paddle.position.x + 30) && (this.x < this.paddle.position.x + 59)) {

        this.setPaddleSound();
        this.setLeftCenterSlope();

      // Center Hit - between 60 and 89 pixels
      } else if ((this.x > this.paddle.position.x - 4) && (this.x > this.paddle.position.x + 60) && (this.x < this.paddle.position.x + 89)) {

        this.setPaddleSound();
        this.setCenterSlope();

      // Right Center Hit - between 90 and 119 pixels
      } else if ((this.x > this.paddle.position.x - 4) && (this.x > this.paddle.position.x + 90) && (this.x < this.paddle.position.x + 119)) {

        this.setPaddleSound();
        this.setRightCenterSlope();

      // Right Hit - between 120 and 154 pixels
      } else if ((this.x > this.paddle.position.x - 4) && (this.x > this.paddle.position.x + 120) && (this.x < this.paddle.position.x + 154)) {

        this.setPaddleSound();
        this.setRightSlope();

      } else {
        this.game.lives -= 1;
        if (this.game.lives > 0){
          this.reset();
        } else {
          alert("You Fell into the Wall - GAME OVER");
          this.setDeathSound();
          document.location.reload();
        }
      }
   }
    this.x += this.dx;
    this.y += this.dy;
  },

  collisionDetectionBricks: function() {
    var self = this;
    var brickHeight = self.bricks[0].size.y;
    var brickWidth = self.bricks[0].size.x;

    for (var i = 0; i < self.bricks.length; i++) {
      if((self.x > self.bricks[i].position.x)
        && (self.x < self.bricks[i].position.x + brickWidth)
        && (self.y - self.radius < self.bricks[i].position.y + brickHeight)
        && (self.y + self.radius > self.bricks[i].position.y - brickHeight)) {
        this.game.brickSound.load();
        this.game.brickSound.play();
        this.dy = -this.dy;
        self.bricks[i].status = 0;
        self.bricks.splice(i, 1);
        self.game.score += 10;
      }
    }
    return this.dy;
  }
};

module.exports = Ball;<|endoftext|>


<commit_before>var Q           = require('q'),
    COLORS      = require('colors'),
    BENCHMARK   = require('benchmark'),
    FS          = require('fs'),
    VM          = require('vm'),
    PATH        = require('path'),
    CP          = require('child_process'),
    INHERIT     = require('inherit'),
    LOGGER      = require('./logger'),
    LEVEL       = require('./level'),
    U           = require('./util'),

    Table       = require('cli-table');

/**
 * Benchmark - testing on BEMHTML templates performance between revisions
 */
var Benchmark = INHERIT({

    __constructor: function(opts, args){

        this.pathTmp        = '.bem/cache/bench/';
        this.pathSelf       = 'current_state';
        this.pathBenchmarks = 'benchmark.bundles';
        this.treeishList    = args['treeish-list'] || [];
        this.withoutCurrent = opts['no-wc'];
        this.rerun          = opts['rerun'];
        this.benchmarks     = opts.benchmarks;

        this.DELAY             = opts['delay'] ? opts['delay'] : 20;
        this.WARMING_CYCLE     = 100;
        this.ALLOWABLE_PERCENT = opts['rme'] ? opts['rme'] : 5;

    },

    /**
     * Extract one revision to pathTmp folder
     *
     * @param  {String} treeish  Hash, HEAD, tag...
     * @return {Promise * String}  Path to temporary revision
     */
    exportGitRevision: function(treeish) {

        var def = Q.defer(),
            label = 'Export git revision [' + treeish.green + ']',
            archiveErr = '',
            extractErr = '',
            extract,
            archive;

        LOGGER.time(label);

        archive = CP.spawn('git', [
            'archive',
            '--format=tar',
            '--prefix=' + this.pathTmp + '/' + treeish + '/',
            treeish
        ]);
        extract = CP.spawn('tar', ['-x']);

        archive.stdout.on('data', function(data) {
            extract.stdin.write(data);
        });

        archive.stderr.on('data', function(err) {
            archiveErr += err.toString().replace(/(\n)$/, '');
        });

        archive.on('close', function(code) {
            if (code !== 0) {
                LOGGER.error('exportGitRevision - '.blue + '[' + treeish.green + '] ' + archiveErr);
                def.reject(new Error(archiveErr));
            } else {
                LOGGER.timeEnd(label);
                def.resolve(treeish);
            }
            extract.stdin.end();
        });

        extract.stderr.on('data', function(err) {
            extractErr += err.toString().replace(/(\n)$/, '');
        });

        extract.on('close', function(code) {
            if (code !== 0) {
                LOGGER.error('exportGitRevision - '.blue + '[' + treeish.green + '] ' + extractErr);
                def.reject(new Error(extractErr));
            }
        });

        return def.promise;

    },

    /**
     * Copy current files state with exclude
     *
     * @return {Promise * String}  Path to files
     */
    exportWorkingCopy: function() {

        var self = this,
            label = 'Export working copy [' + self.pathSelf.green + ']',
            cmd = [
                'rsync',
                '-a',
                '--exclude=/.git',
                '--exclude=/.svn',
                '--exclude=/' + self.pathTmp,
                '.',
                self.pathTmp + self.pathSelf
            ].join(' ');

        LOGGER.time(label);

        return U.exec(cmd)
            .fail(function(err) {
                LOGGER.error('exportWorkingCopy'.blue);
                return Q.reject(err);
            })
            .then(function() {
                LOGGER.timeEnd(label);
                return self.pathSelf;
            });

    },

    /**
     *  Getting all paths to .bemjson.js file on needed level
     *
     *  @param  {String} treeishPath  Checked out treeish path
     *  @param  {String} levelPath  level path
     *  @return {Array}  Array of links
     */
    getBenchmarksPaths: function(treeishPath, levelPath) {

        var self = this,
            level =  LEVEL.createLevel(PATH.join(treeishPath, levelPath)),
            benchmarks;

        if (self.benchmarks && self.benchmarks.length) {
            // if -b flag detected then compare not all benchmarks
            benchmarks = U.arrayUnique(self.benchmarks)
                .map(function(b) {
                    return U.bemParseKey(b);
                });
        }
        else {
            // if -b flag not detected then all benchmarks
            benchmarks = level.getItemsByIntrospection()
                .filter(function(item) {
                    return item.tech == 'bemjson.js';
                });
        }

        return benchmarks
            .map(function(benchmark) {
                return level.getPathByObj(benchmark, 'bemjson.js');
            });

    },

    /**
     * Make one target and return all links on blocks
     *
     * @param  {String} target  Path to target
     * @return {Array}  All links of target
     */
    makeTarget: function(target) {

        var self = this,
            targets = [self.pathBenchmarks],
            label = '[' + target.green + '] has been assembled';

        LOGGER.time(label);

        if (self.benchmarks && self.benchmarks.length) {
            targets = self.benchmarks
                .map(function(b) {
                    return PATH.join(self.pathBenchmarks, b);
                });
        }

        return U.exec('cd ' + PATH.join(self.pathTmp, target) + ' && npm install && ./node_modules/.bin/bem make ' + targets.join(' '))
            .fail(function(err) {
                LOGGER.error(target.red + ' not make');
                return Q.reject(err);
            })
            .then(function() {
                LOGGER.timeEnd(label);
            });

    },

    /**
     * Make all targets and run benchmarks for each target
     *
     * @param  {Array} targets  List of targets
     * @return {Promise * Undefined}  Object with tests results
     */
    makeAll: function(targets) {
        return Q.all(targets.map(this.makeTarget.bind(this)))
            .then(function() {});
    },

    /**
     * Active waiting
     *
     * @param  {Number} seconds  Time in seconds to wait
     * @return {Promise * Undefined}
     */
    activeWait: function(seconds) {

        if (seconds == 0) return;

        var dt = new Date().getTime(),
            title = seconds + 'sec';

        LOGGER.info('Delay ' + title.red);
        while (dt + (seconds * 1000) > new Date().getTime()) {}

    },

    /**
     * Run all benchmarks of the single target
     *
     * @param  {Array} links  Links to source bemjson files
     * @param  {String} target  Revision name
     * @return {Promise * Object}  Object with tests results
     */
    runBenchmark: function(links, target) {

        var self = this;

        return Q.all(links.map(function(link) {

            var def = Q.defer(),
                bemjson = FS.readFileSync(link , 'UTF-8'),
                bemhtml = require(link.replace(/.bemjson.js/, '.bemhtml.js')),
                context = VM.createContext({}),
                res = VM.runInContext(bemjson, context, link),
                name = link.match(/([^\/]+)$/)[0],
                name_short = name.replace('.bemjson.js',''),
                suite = new BENCHMARK.Suite(),

                label = '[' + target.green + ' => ' +name_short.blue+ '] has been tested';

            LOGGER.time(label);

            // Warming-up for the best results
            (function() {
                var i = self.WARMING_CYCLE;
                while (i--) bemhtml.BEMHTML.apply(res);
            })();

            suite
                .add(name, function() {
                    bemhtml.BEMHTML.apply(res);
                })
                .on('complete', function(event) {
                    LOGGER.timeEnd(label);
                    def.resolve({
                        'name' : String(event.target.name),
                        'hz'   : Number(Math.round(event.target.hz) / 1000).toFixed(1),
                        'rme'  : Number(event.target.stats.rme).toFixed(1),
                        'runs' : event.target.stats.sample.length
                    });
                })
                .run({ 'async': false });

            return def.promise;

        }))
        .then(function(res) {
            return [target].concat(res);
        });

    },

    /**
     * Extract all revisions and mark that it exist
     *
     * @return {Promise * Undefined}
     */
    exportGitRevisions: function() {

        var self = this;

        return self.treeishList
            .map(function(treeish) {
                return self.exportGitRevision(treeish);
            });

    },

    /**
     * Copy reference benchmarks from specified source into every checked out revision
     *
     * @param  {String[]} targets  Array of target names
     * @param  {String} source  Path to benchmarks source
     * @return {Promise * Undefined}
     */
    cloneBenchmarks: function(source, targets) {

        targets = [].concat(targets);

        var self = this,
            search = targets.indexOf(source),

            onFail = function(err) {
                LOGGER.error('cloneBenchmarks - '.blue + err);
                return Q.reject(err);
            };

        if (search !== -1) {
            targets.splice(search, 1);
        }

        return Q.all(targets.map(function(target) {

            var cmd = 'rm -rf ' + PATH.join(self.pathTmp, target, self.pathBenchmarks);

            return U.exec(cmd)
                .fail(onFail)
                .then(function() {

                    var cmd = [
                        'cp',
                        '-R',
                        PATH.join(self.pathTmp, source, self.pathBenchmarks + '*'),
                        PATH.join(self.pathTmp, target, self.pathBenchmarks)
                    ].join(' ');

                    return U.exec(cmd)
                        .fail(onFail);

                });

        }));

    },

    /**
     * Return latest treeish compared at date
     *
     * @return {String}  Latest treeish
     */
    getLatestTreeish: function() {

        var self = this;

        if (!self.withoutCurrent) {
            LOGGER.info('Latest revision is - ', self.pathSelf.magenta);
            return Q.resolve(self.pathSelf);
        }

        return Q.all(self.treeishList
            .map(function(treeish) {

                var cmd = "git show " + treeish + " | grep Date | awk -F':   ' '{print $2}'";

                return U.exec(cmd, null, true)
                    .then(function(output) {
                        return {
                            treeish: treeish,
                            date: output.replace('\n', '')
                        };
                    });

            })
        )
        .then(function(dates) {

            var maxTime = 0,
                lastTreeish;

            dates.forEach(function(dt) {
                var time = new Date(dt.date).getTime();
                if (time > maxTime) {
                    maxTime = time;
                    lastTreeish = dt.treeish;
                }
            });

            LOGGER.info('Latest revision is - ' + lastTreeish.magenta);

            return lastTreeish;

        });

    },

    /**
     * Sort results by command line order
     *
     * @param  {Object} res  Results data
     * @param  {String[]} targets  Array of target names
     * @return {Array}  Sorted results data
     */
    sortResult: function(res, targets) {
        var sorted = [];

        for (var i = 0; i < res.length; i++) {
            for (var j = 0; j < res.length; j++) {
                if (res[j][0] === targets[i]) {
                    sorted.push(res[j]);
                    break;
                }
            }
        }

        return sorted;
    },

    /**
     * Construct cli table object with benchmarks results
     *
     * @param  {Object} results  Object with test results
     * @param  {String[]} targets  Array of target names
     * @return {Object}  cli table object
     */
    getResultsTable: function(results, targets) {

        var sortedRes = this.sortResult(results, targets),
            header = [],
            data = [],
            rme = [],
            name,
            stat,
            perc,
            t;

        header.push(
            '№'.magenta,
            'benchmark'.yellow.underline);

        sortedRes.forEach(function(item) {
            header.push(item[0].blue + '(hz/rme)'.cyan);
        });

        for (var i = 1; i < sortedRes[0].length; i += 1) {
            name = undefined;
            t = [];
            rme = [];

            for (var j = 0; j < sortedRes.length; j++) {
                if (!name) name = sortedRes[j][i].name;

                if (sortedRes[j][i]) {
                    t.push('[' + sortedRes[j][i].hz.green + '] ±' + sortedRes[j][i].rme.magenta + '%');
                    rme.push(Number(sortedRes[j][i].rme));
                } else {
                    t.push('none');
                }
            }

            var max = Math.max.apply(null, rme),
                min = Math.min.apply(null, rme);

            if ((max - min) < this.ALLOWABLE_PERCENT) {
                perc = 'stable'.yellow.underline;
            } else {
                perc = 'unstable'.red.underline;
            }

            data.push([i, name.replace('.bemjson.js', '')]
                .concat(t)
                .concat(perc));
        }

        header.push('RME stat'.magenta);

        var table = new Table({
            head: header,
            style: {
                compact: true,
                'padding-left': 1,
                'padding-right': 1
            }
        });

        data.forEach(function(row) {
            table.push(row);
        });

        return table;

    },

    /**
     * Cleaning all repo from temporary folder
     *
     * @return {Promise * Undefined}
     */
    cleanTempDir: function() {

        var cmd = 'rm -rf ./' + this.pathTmp + '*',
            label = 'TMP folder has been cleaned';

        LOGGER.info('Cleaning a TMP folder');
        LOGGER.time(label);

        return U.exec(cmd)
            .fin(function() {
                LOGGER.timeEnd(label);
            });

    },

    /**
     * Main flow
     *
     * @return {Promise}  Promise of benchmarks finish
     */
    start: function() {

        var self = this,
            bcsInclude = 'Include ' + String(this.pathSelf).magenta,
            bcsExclude = 'Exclude ' + String(this.pathSelf).magenta,
            targets = [].concat(self.treeishList);

        LOGGER.time('All time');

        if (!self.withoutCurrent) {
            LOGGER.info(bcsInclude);
            targets.push(self.pathSelf);
        } else {
            LOGGER.info(bcsExclude);
        }

        return Q.resolve()
            .then(function() {

                if (self.rerun) {

                    // without make
                    LOGGER.info('[NO MAKE]'.magenta + ' mode');
                    return Q.resolve();

                }
                else {

                    // export and make revisions
                    return U.mkdirp(self.pathTmp)
                        .then(function() {
                            return self.cleanTempDir();
                        })
                        .then(function() {

                            var exports = self.exportGitRevisions();
                            if (!self.withoutCurrent) {
                                exports.push(self.exportWorkingCopy());
                            }

                            return Q.all(exports)
                                .then(function() {
                                    return self.getLatestTreeish()
                                        .then(function(rev) {
                                            return self.cloneBenchmarks(rev, targets);
                                        });
                                })
                                .then(function() {
                                    LOGGER.info('Make...');
                                    return self.makeAll(targets);
                                });

                        });

                }

            })
            .then(function() {
                LOGGER.info('Benchmark...');

                return Q.all(targets
                    .map(function(target) {

                        var links = self.getBenchmarksPaths(
                            PATH.join(self.pathTmp, target),
                            self.pathBenchmarks);

                        self.activeWait(self.DELAY);
                        return self.runBenchmark(links, target);

                    })
                );

            })
            .then(function(res) {
                LOGGER.timeEnd('All time');
                return self.getResultsTable(res, targets);
            });

    }

});

/**
 * Create instance on Benchmark and share it
 *
 * @param  {Array} opts Options from COA
 * @param  {Array} args Arguments from COA
 * @return {Object} Benchmark instance
 */
module.exports = function(opts, args) {
    return new Benchmark(opts, args);
};
<commit_msg>Modify method `runBenchmark()` for BH engine<commit_after>var Q           = require('q'),
    COLORS      = require('colors'),
    BENCHMARK   = require('benchmark'),
    FS          = require('fs'),
    VM          = require('vm'),
    PATH        = require('path'),
    CP          = require('child_process'),
    INHERIT     = require('inherit'),
    LOGGER      = require('./logger'),
    LEVEL       = require('./level'),
    U           = require('./util'),

    Table       = require('cli-table');

/**
 * Benchmark - testing on BEMHTML templates performance between revisions
 */
var Benchmark = INHERIT({

    __constructor: function(opts, args){

        this.pathTmp        = '.bem/cache/bench/';
        this.pathSelf       = 'current_state';
        this.pathBenchmarks = 'benchmark.bundles';
        this.treeishList    = args['treeish-list'] || [];
        this.withoutCurrent = opts['no-wc'];
        this.rerun          = opts['rerun'];
        this.benchmarks     = opts.benchmarks;

        this.DELAY             = opts['delay'] ? opts['delay'] : 20;
        this.WARMING_CYCLE     = 100;
        this.ALLOWABLE_PERCENT = opts['rme'] ? opts['rme'] : 5;

    },

    /**
     * Extract one revision to pathTmp folder
     *
     * @param  {String} treeish  Hash, HEAD, tag...
     * @return {Promise * String}  Path to temporary revision
     */
    exportGitRevision: function(treeish) {

        var def = Q.defer(),
            label = 'Export git revision [' + treeish.green + ']',
            archiveErr = '',
            extractErr = '',
            extract,
            archive;

        LOGGER.time(label);

        archive = CP.spawn('git', [
            'archive',
            '--format=tar',
            '--prefix=' + this.pathTmp + '/' + treeish + '/',
            treeish
        ]);
        extract = CP.spawn('tar', ['-x']);

        archive.stdout.on('data', function(data) {
            extract.stdin.write(data);
        });

        archive.stderr.on('data', function(err) {
            archiveErr += err.toString().replace(/(\n)$/, '');
        });

        archive.on('close', function(code) {
            if (code !== 0) {
                LOGGER.error('exportGitRevision - '.blue + '[' + treeish.green + '] ' + archiveErr);
                def.reject(new Error(archiveErr));
            } else {
                LOGGER.timeEnd(label);
                def.resolve(treeish);
            }
            extract.stdin.end();
        });

        extract.stderr.on('data', function(err) {
            extractErr += err.toString().replace(/(\n)$/, '');
        });

        extract.on('close', function(code) {
            if (code !== 0) {
                LOGGER.error('exportGitRevision - '.blue + '[' + treeish.green + '] ' + extractErr);
                def.reject(new Error(extractErr));
            }
        });

        return def.promise;

    },

    /**
     * Copy current files state with exclude
     *
     * @return {Promise * String}  Path to files
     */
    exportWorkingCopy: function() {

        var self = this,
            label = 'Export working copy [' + self.pathSelf.green + ']',
            cmd = [
                'rsync',
                '-a',
                '--exclude=/.git',
                '--exclude=/.svn',
                '--exclude=/' + self.pathTmp,
                '.',
                self.pathTmp + self.pathSelf
            ].join(' ');

        LOGGER.time(label);

        return U.exec(cmd)
            .fail(function(err) {
                LOGGER.error('exportWorkingCopy'.blue);
                return Q.reject(err);
            })
            .then(function() {
                LOGGER.timeEnd(label);
                return self.pathSelf;
            });

    },

    /**
     *  Getting all paths to .bemjson.js file on needed level
     *
     *  @param  {String} treeishPath  Checked out treeish path
     *  @param  {String} levelPath  level path
     *  @return {Array}  Array of links
     */
    getBenchmarksPaths: function(treeishPath, levelPath) {

        var self = this,
            level =  LEVEL.createLevel(PATH.join(treeishPath, levelPath)),
            benchmarks;

        if (self.benchmarks && self.benchmarks.length) {
            // if -b flag detected then compare not all benchmarks
            benchmarks = U.arrayUnique(self.benchmarks)
                .map(function(b) {
                    return U.bemParseKey(b);
                });
        }
        else {
            // if -b flag not detected then all benchmarks
            benchmarks = level.getItemsByIntrospection()
                .filter(function(item) {
                    return item.tech == 'bemjson.js';
                });
        }

        return benchmarks
            .map(function(benchmark) {
                return level.getPathByObj(benchmark, 'bemjson.js');
            });

    },

    /**
     * Make one target and return all links on blocks
     *
     * @param  {String} target  Path to target
     * @return {Array}  All links of target
     */
    makeTarget: function(target) {

        var self = this,
            targets = [self.pathBenchmarks],
            label = '[' + target.green + '] has been assembled';

        LOGGER.time(label);

        if (self.benchmarks && self.benchmarks.length) {
            targets = self.benchmarks
                .map(function(b) {
                    return PATH.join(self.pathBenchmarks, b);
                });
        }

        return U.exec('cd ' + PATH.join(self.pathTmp, target) + ' && npm install && ./node_modules/.bin/bem make ' + targets.join(' '))
            .fail(function(err) {
                LOGGER.error(target.red + ' not make');
                return Q.reject(err);
            })
            .then(function() {
                LOGGER.timeEnd(label);
            });

    },

    /**
     * Make all targets and run benchmarks for each target
     *
     * @param  {Array} targets  List of targets
     * @return {Promise * Undefined}  Object with tests results
     */
    makeAll: function(targets) {
        return Q.all(targets.map(this.makeTarget.bind(this)))
            .then(function() {});
    },

    /**
     * Active waiting
     *
     * @param  {Number} seconds  Time in seconds to wait
     * @return {Promise * Undefined}
     */
    activeWait: function(seconds) {

        if (seconds == 0) return;

        var dt = new Date().getTime(),
            title = seconds + 'sec';

        LOGGER.info('Delay ' + title.red);
        while (dt + (seconds * 1000) > new Date().getTime()) {}

    },

    /**
     * Run all benchmarks of the single target
     *
     * @param  {Array} links  Links to source bemjson files
     * @param  {String} target  Revision name
     * @return {Promise * Object}  Object with tests results
     */
    runBenchmark: function(links, target) {

        var self = this;

        return Q.all(links.map(function(link) {

            var def = Q.defer(),
                bemjson = FS.readFileSync(link , 'UTF-8'),
                bemhtml = require(link.replace(/.bemjson.js/, '.bemhtml.js')),
                bh = require(link.replace(/.bemjson.js/, '.bh.js')),
                context = VM.createContext({}),
                res = VM.runInContext(bemjson, context, link),
                name = link.match(/([^\/]+)$/)[0],
                name_short = name.replace('.bemjson.js',''),
                suite = new BENCHMARK.Suite(),
                results = [],

                label = '[' + target.green + ' => ' +name_short.blue+ '] has been tested';

            LOGGER.time(label);

            // Warming-up for the best results
            (function() {
                var i = self.WARMING_CYCLE;
                while (i--) {
                    bemhtml.BEMHTML.apply(res);
                    bh.INST.apply(res);
                }
            })();

            suite
                .add(name + '(bemhtml)'.underline, function() {
                    bemhtml.BEMHTML.apply(res);
                })
                .add(name + '(bh)'.underline, function() {
                    bh.INST.apply(res);
                })
                .on('cycle', function(event) {
                    results.push({
                        'name' : String(event.target.name),
                        'hz'   : Number(Math.round(event.target.hz) / 1000).toFixed(1),
                        'rme'  : Number(event.target.stats.rme).toFixed(1),
                        'runs' : event.target.stats.sample.length
                    });
                })
                .on('complete', function(event) {
                    LOGGER.timeEnd(label);
                    def.resolve(results);
                })
                .on('error', function(err) {
                    def.reject(err);
                })
                .run({ 'async': false });

            return def.promise;

        }))
        .then(function(rs) {
            var objs = [];

            rs.forEach(function(i) {
                i.forEach(function(ii) {
                    objs.push(ii);
                });
            });

            return [target].concat(objs);
        });

    },

    /**
     * Extract all revisions and mark that it exist
     *
     * @return {Promise * Undefined}
     */
    exportGitRevisions: function() {

        var self = this;

        return self.treeishList
            .map(function(treeish) {
                return self.exportGitRevision(treeish);
            });

    },

    /**
     * Copy reference benchmarks from specified source into every checked out revision
     *
     * @param  {String[]} targets  Array of target names
     * @param  {String} source  Path to benchmarks source
     * @return {Promise * Undefined}
     */
    cloneBenchmarks: function(source, targets) {

        targets = [].concat(targets);

        var self = this,
            search = targets.indexOf(source),

            onFail = function(err) {
                LOGGER.error('cloneBenchmarks - '.blue + err);
                return Q.reject(err);
            };

        if (search !== -1) {
            targets.splice(search, 1);
        }

        return Q.all(targets.map(function(target) {

            var cmd = 'rm -rf ' + PATH.join(self.pathTmp, target, self.pathBenchmarks);

            return U.exec(cmd)
                .fail(onFail)
                .then(function() {

                    var cmd = [
                        'cp',
                        '-R',
                        PATH.join(self.pathTmp, source, self.pathBenchmarks + '*'),
                        PATH.join(self.pathTmp, target, self.pathBenchmarks)
                    ].join(' ');

                    return U.exec(cmd)
                        .fail(onFail);

                });

        }));

    },

    /**
     * Return latest treeish compared at date
     *
     * @return {String}  Latest treeish
     */
    getLatestTreeish: function() {

        var self = this;

        if (!self.withoutCurrent) {
            LOGGER.info('Latest revision is - ', self.pathSelf.magenta);
            return Q.resolve(self.pathSelf);
        }

        return Q.all(self.treeishList
            .map(function(treeish) {

                var cmd = "git show " + treeish + " | grep Date | awk -F':   ' '{print $2}'";

                return U.exec(cmd, null, true)
                    .then(function(output) {
                        return {
                            treeish: treeish,
                            date: output.replace('\n', '')
                        };
                    });

            })
        )
        .then(function(dates) {

            var maxTime = 0,
                lastTreeish;

            dates.forEach(function(dt) {
                var time = new Date(dt.date).getTime();
                if (time > maxTime) {
                    maxTime = time;
                    lastTreeish = dt.treeish;
                }
            });

            LOGGER.info('Latest revision is - ' + lastTreeish.magenta);

            return lastTreeish;

        });

    },

    /**
     * Sort results by command line order
     *
     * @param  {Object} res  Results data
     * @param  {String[]} targets  Array of target names
     * @return {Array}  Sorted results data
     */
    sortResult: function(res, targets) {
        var sorted = [];

        for (var i = 0; i < res.length; i++) {
            for (var j = 0; j < res.length; j++) {
                if (res[j][0] === targets[i]) {
                    sorted.push(res[j]);
                    break;
                }
            }
        }

        return sorted;
    },

    /**
     * Construct cli table object with benchmarks results
     *
     * @param  {Object} results  Object with test results
     * @param  {String[]} targets  Array of target names
     * @return {Object}  cli table object
     */
    getResultsTable: function(results, targets) {

        var sortedRes = this.sortResult(results, targets),
            header = [],
            data = [],
            rme = [],
            name,
            stat,
            perc,
            t;

        header.push(
            '№'.magenta,
            'benchmark'.yellow.underline);

        sortedRes.forEach(function(item) {
            header.push(item[0].blue + '(hz/rme)'.cyan);
        });

        for (var i = 1; i < sortedRes[0].length; i += 1) {
            name = undefined;
            t = [];
            rme = [];

            for (var j = 0; j < sortedRes.length; j++) {
                if (!name) name = sortedRes[j][i].name;

                if (sortedRes[j][i]) {
                    t.push('[' + sortedRes[j][i].hz.green + '] ±' + sortedRes[j][i].rme.magenta + '%');
                    rme.push(Number(sortedRes[j][i].rme));
                } else {
                    t.push('none');
                }
            }

            var max = Math.max.apply(null, rme),
                min = Math.min.apply(null, rme);

            if ((max - min) < this.ALLOWABLE_PERCENT) {
                perc = 'stable'.yellow.underline;
            } else {
                perc = 'unstable'.red.underline;
            }

            data.push([i, name.replace('.bemjson.js', '')]
                .concat(t)
                .concat(perc));
        }

        header.push('RME stat'.magenta);

        var table = new Table({
            head: header,
            style: {
                compact: true,
                'padding-left': 1,
                'padding-right': 1
            }
        });

        data.forEach(function(row) {
            table.push(row);
        });

        return table;

    },

    /**
     * Cleaning all repo from temporary folder
     *
     * @return {Promise * Undefined}
     */
    cleanTempDir: function() {

        var cmd = 'rm -rf ./' + this.pathTmp + '*',
            label = 'TMP folder has been cleaned';

        LOGGER.info('Cleaning a TMP folder');
        LOGGER.time(label);

        return U.exec(cmd)
            .fin(function() {
                LOGGER.timeEnd(label);
            });

    },

    /**
     * Main flow
     *
     * @return {Promise}  Promise of benchmarks finish
     */
    start: function() {

        var self = this,
            bcsInclude = 'Include ' + String(this.pathSelf).magenta,
            bcsExclude = 'Exclude ' + String(this.pathSelf).magenta,
            targets = [].concat(self.treeishList);

        LOGGER.time('All time');

        if (!self.withoutCurrent) {
            LOGGER.info(bcsInclude);
            targets.push(self.pathSelf);
        } else {
            LOGGER.info(bcsExclude);
        }

        return Q.resolve()
            .then(function() {

                if (self.rerun) {

                    // without make
                    LOGGER.info('[NO MAKE]'.magenta + ' mode');
                    return Q.resolve();

                }
                else {

                    // export and make revisions
                    return U.mkdirp(self.pathTmp)
                        .then(function() {
                            return self.cleanTempDir();
                        })
                        .then(function() {

                            var exports = self.exportGitRevisions();
                            if (!self.withoutCurrent) {
                                exports.push(self.exportWorkingCopy());
                            }

                            return Q.all(exports)
                                .then(function() {
                                    return self.getLatestTreeish()
                                        .then(function(rev) {
                                            return self.cloneBenchmarks(rev, targets);
                                        });
                                })
                                .then(function() {
                                    LOGGER.info('Make...');
                                    return self.makeAll(targets);
                                });

                        });

                }

            })
            .then(function() {
                LOGGER.info('Benchmark...');

                return Q.all(targets
                    .map(function(target) {

                        var links = self.getBenchmarksPaths(
                            PATH.join(self.pathTmp, target),
                            self.pathBenchmarks);

                        self.activeWait(self.DELAY);
                        return self.runBenchmark(links, target);

                    })
                );

            })
            .then(function(res) {
                LOGGER.timeEnd('All time');
                return self.getResultsTable(res, targets);
            });

    }

});

/**
 * Create instance on Benchmark and share it
 *
 * @param  {Array} opts Options from COA
 * @param  {Array} args Arguments from COA
 * @return {Object} Benchmark instance
 */
module.exports = function(opts, args) {
    return new Benchmark(opts, args);
};
<|endoftext|>