<commit_before>var path = require('path')
var fs = require('fs')
var spawn = require('child_process').spawn
var cache = require('npm-cache-filename')
var log = require('npmlog')
var rimraf = require('rimraf')
var Download = require('download')
var tmp = path.join(require('os').tmpdir(), 'cache')
var mkdirp = require('mkdirp')
var pack = require('tar-pack').pack
var unpack = require('tar-pack').unpack

function NapaPkg(url, name, opts) {
  if (!(this instanceof NapaPkg)) return new NapaPkg(url, name, opts)
  var self = this
  opts = opts || {}
  this.cwd = opts.cwd || process.cwd()
  this.log = opts.log || log
  this._mock = opts._mock
  this.ref = opts.ref
  this.url = url
  this.name = name
  this.installTo = path.join(this.cwd, 'node_modules', this.name)
  this.cacheTo = cache(tmp, this.url)
  this._napaResolvedKey = '_napaResolved'

  Object.defineProperty(self, 'installed', {
    get: function() {
      var existing = path.join(self.installTo, 'package.json')
      return (fs.existsSync(existing) && require(existing)[self._napaResolvedKey] === url)
    }
  })
  Object.defineProperty(self, 'cached', {
    get: function() { return fs.existsSync(self.cacheTo) }
  })
}
module.exports = NapaPkg

NapaPkg.prototype.install = function(done) {
  var self = this, cbCalled = false
  done = done || function() {}

  // Do nothing if already installed
  if (self.installed) return done()

  function cb(err) {
    if (err) return done(err.message)
    self.writePackageJson(function(err) {
      if (err) return done(err.message)
      self.cache(done)
    })
  }

  function cacheInstall() {
    if (typeof self._mock === 'function') {
      self._mock(['cache', self.url, self.name])
    } else {
      self.log.info('cache', '%s into %s', self.url, self.name)
      fs.createReadStream(self.cacheTo)
        .pipe(unpack(self.installTo, cb))
    }
  }

  function gitInstall() {
    var args = ['clone', '--depth', '1', '-q', (self.url.replace('git+', '')), self.installTo]
    var cmd = ['git', args]
    if (self.ref) args.splice(1, 2)
    if (typeof self._mock === 'function') {
      self._mock(cmd)
    } else {
      self.log.info('git', '%s into %s', self.url, self.name)
      var git = spawn.apply(spawn, cmd)
      git.stderr.on('data', log.error)
      git.on('close', function(code, signal){
        var checkout
        if (code) return cb(code, signal)
        if (self.ref) {
          checkout = spawn('git', ['checkout', self.ref], {cwd: self.installTo})
          checkout.stderr.on('data', log.info)
          checkout.on('close', cb)
        }
      })
    }
  }

  function downloadInstall() {
    if (typeof self._mock === 'function') {
      self._mock(['download', self.url, self.installTo])
    } else {
      self.log.info('download', '%s into %s', self.url, self.name)
      new Download({ extract: true, strip: 1 })
        .get(self.url)
        .dest(self.installTo)
        .run(cb)
    }
  }

  // is this a git repo url?
  var gitUrls = ['git+', 'git://']
  var githubDownloadUrls = /github\.com(?:\/[^\/]+){2}($|#)/

  // Determine which type of install we would like
  rimraf(self.installTo, function(err) {
    if (err) log.error(err.message)
    if (fs.existsSync(self.cacheTo)) {
      return cacheInstall()
    } else if (gitUrls.indexOf(self.url.slice(0, 4)) !== -1) {
      return gitInstall()
    } else {
      if (githubDownloadUrls.test(self.url)) {
        return gitInstall()
      }
      return downloadInstall()
    }
  })
}

// Caches a locally installed package
NapaPkg.prototype.cache = function(done) {
  var self = this
  if (!this.installed) return done()
  mkdirp(path.dirname(self.cacheTo), function(err) {
    if (err) return done(err)
    var dest = fs.createWriteStream(self.cacheTo)
    pack(self.installTo, {ignoreFiles: []})
      .pipe(dest)
      .on('close', done)
  })
}

// TODO: Replace this with metamorph and ability to override
NapaPkg.prototype.writePackageJson = function(done) {
  var filepath = path.join(this.installTo, 'package.json')
  var pkg = null
  if (!fs.existsSync(filepath)) {
    pkg = {
      name: this.name,
      version: '0.0.0',
      description: ''
    }
  } else {
    pkg = require(filepath)
  }
  pkg[this._napaResolvedKey] = this.url
  fs.writeFile(filepath, JSON.stringify(pkg, null, 2), done)
}
<commit_msg>Rename variable<commit_after>var path = require('path')
var fs = require('fs')
var spawn = require('child_process').spawn
var cache = require('npm-cache-filename')
var log = require('npmlog')
var rimraf = require('rimraf')
var Download = require('download')
var tmp = path.join(require('os').tmpdir(), 'cache')
var mkdirp = require('mkdirp')
var pack = require('tar-pack').pack
var unpack = require('tar-pack').unpack

function NapaPkg(url, name, opts) {
  if (!(this instanceof NapaPkg)) return new NapaPkg(url, name, opts)
  var self = this
  opts = opts || {}
  this.cwd = opts.cwd || process.cwd()
  this.log = opts.log || log
  this._mock = opts._mock
  this.ref = opts.ref
  this.url = url
  this.name = name
  this.installTo = path.join(this.cwd, 'node_modules', this.name)
  this.cacheTo = cache(tmp, this.url)
  this._napaResolvedKey = '_napaResolved'

  Object.defineProperty(self, 'installed', {
    get: function() {
      var existing = path.join(self.installTo, 'package.json')
      return (fs.existsSync(existing) && require(existing)[self._napaResolvedKey] === url)
    }
  })
  Object.defineProperty(self, 'cached', {
    get: function() { return fs.existsSync(self.cacheTo) }
  })
}
module.exports = NapaPkg

NapaPkg.prototype.install = function(done) {
  var self = this, cbCalled = false
  done = done || function() {}

  // Do nothing if already installed
  if (self.installed) return done()

  function cb(err) {
    if (err) return done(err.message)
    self.writePackageJson(function(err) {
      if (err) return done(err.message)
      self.cache(done)
    })
  }

  function cacheInstall() {
    if (typeof self._mock === 'function') {
      self._mock(['cache', self.url, self.name])
    } else {
      self.log.info('cache', '%s into %s', self.url, self.name)
      fs.createReadStream(self.cacheTo)
        .pipe(unpack(self.installTo, cb))
    }
  }

  function gitInstall() {
    var args = ['clone', '--depth', '1', '-q', (self.url.replace('git+', '')), self.installTo]
    var cmd = ['git', args]
    if (self.ref) args.splice(1, 2)
    if (typeof self._mock === 'function') {
      self._mock(cmd)
    } else {
      self.log.info('git', '%s into %s', self.url, self.name)
      var git = spawn.apply(spawn, cmd)
      git.stderr.on('data', log.error)
      git.on('close', function(code, signal){
        var checkout
        if (code) return cb(code, signal)
        if (self.ref) {
          checkout = spawn('git', ['checkout', self.ref], {cwd: self.installTo})
          checkout.stderr.on('data', log.info)
          checkout.on('close', cb)
        }
      })
    }
  }

  function downloadInstall() {
    if (typeof self._mock === 'function') {
      self._mock(['download', self.url, self.installTo])
    } else {
      self.log.info('download', '%s into %s', self.url, self.name)
      new Download({ extract: true, strip: 1 })
        .get(self.url)
        .dest(self.installTo)
        .run(cb)
    }
  }

  // is this a git repo url?
  var gitUrls = ['git+', 'git://']
  var githubRepoUrls = /github\.com(?:\/[^\/]+){2}($|#)/

  // Determine which type of install we would like
  rimraf(self.installTo, function(err) {
    if (err) log.error(err.message)
    if (fs.existsSync(self.cacheTo)) {
      return cacheInstall()
    } else if (gitUrls.indexOf(self.url.slice(0, 4)) !== -1) {
      return gitInstall()
    } else {
      if (githubRepoUrls.test(self.url)) {
        return gitInstall()
      }
      return downloadInstall()
    }
  })
}

// Caches a locally installed package
NapaPkg.prototype.cache = function(done) {
  var self = this
  if (!this.installed) return done()
  mkdirp(path.dirname(self.cacheTo), function(err) {
    if (err) return done(err)
    var dest = fs.createWriteStream(self.cacheTo)
    pack(self.installTo, {ignoreFiles: []})
      .pipe(dest)
      .on('close', done)
  })
}

// TODO: Replace this with metamorph and ability to override
NapaPkg.prototype.writePackageJson = function(done) {
  var filepath = path.join(this.installTo, 'package.json')
  var pkg = null
  if (!fs.existsSync(filepath)) {
    pkg = {
      name: this.name,
      version: '0.0.0',
      description: ''
    }
  } else {
    pkg = require(filepath)
  }
  pkg[this._napaResolvedKey] = this.url
  fs.writeFile(filepath, JSON.stringify(pkg, null, 2), done)
}
<|endoftext|>


<commit_before>/**
 * pty.js
 * Copyright (c) 2012, Christopher Jeffrey (MIT License)
 * Binding to the pseudo terminals.
 */

var net = require('net');
var pty = require('../build/Release/pty.node');

/**
 * Terminal
 */

// Example:
//  var term = new Terminal('bash', ['-i'], {
//    name: 'xterm-color',
//    cols: 80,
//    rows: 30,
//    cwd: process.env.HOME,
//    env: { HELLO: 'WORLD' }
//  });

var Terminal = function(file, args, opt) {
  if (!(this instanceof Terminal)) return new Terminal(file, args, opt);

  var self = this
    , env
    , cwd
    , name
    , cols
    , rows
    , term;

  // backward compatibility
  if (typeof args === 'string') {
    opt = {
      name: arguments[1],
      cols: arguments[2],
      rows: arguments[3],
      cwd: process.env.HOME
    };
    args = [];
  }

  // arguments
  args = args || [];
  file = file || 'sh';
  opt = opt || {};

  name = opt.name || 'vt100';
  cols = opt.cols || 80;
  rows = opt.rows || 30;
  env = opt.env || clone(process.env);
  cwd = opt.cwd || process.cwd();

  if (opt.name) env.TERM = name;
  env = environ(env);

  // fork
  term = pty.fork(file, args, env, cwd, cols, rows);
  this.socket = new net.Socket(term.fd);
  this.socket.setEncoding('utf8');
  this.socket.resume();

  // setup
  this.socket.on('error', function(err) {
    self.socket.writable = false;
    self.write = function() {};
    self.end = self.write;
    self.writable = false;

    // EIO, happens when someone closes our child
    // process: the only process in the terminal.
    if (err.code && ~err.code.indexOf('errno 5')) return;

    // throw anything else
    if (self.listeners('error').length < 2) {
      throw err;
    }
  });

  this.pid = term.pid;
  this.fd = term.fd;
  this.pty = term.pty;

  this.file = file;
  this.name = name;
  this.cols = cols;
  this.rows = rows;

  env = null;
};

Terminal.fork =
Terminal.spawn =
Terminal.createTerminal = function(file, args, opt) {
  return new Terminal(file, args, opt);
};

/**
 * openpty
 */

Terminal.open = function(opt) {
  var self = Object.create(Terminal.prototype)
    , opt = opt || {};

  if (arguments.length > 1) {
    opt = {
      cols: arguments[1],
      rows: arguments[2]
    };
  }

  var cols = opt.cols || 80
    , rows = opt.rows || 30
    , term;

  // open
  term = pty.open(cols, rows);

  self.master = new net.Socket(term.master);
  self.master.setEncoding('utf8');
  self.master.resume();

  self.slave = new net.Socket(term.slave);
  self.slave.setEncoding('utf8');
  self.slave.resume();

  self.socket = self.master;
  self.pid = null;
  self.fd = term.master;
  self.pty = term.pty;

  self.file = process.argv[0] || 'node';
  self.name = process.env.TERM || '';
  self.cols = cols;
  self.rows = rows;

  return self;
};

/**
 * Events
 */

// don't inherit from net.Socket in
// order to avoid collisions.

Terminal.prototype.write = function(data) {
  return this.socket.write(data);
};

Terminal.prototype.end = function(data) {
  return this.socket.end(data);
};

Terminal.prototype.pipe = function(dest, options) {
  return this.socket.pipe(dest, options);
};

Terminal.prototype.addListener =
Terminal.prototype.on = function(type, func) {
  this.socket.on(type, func);
  return this;
};

Terminal.prototype.emit = function() {
  return this.socket.emit.apply(this.socket, arguments);
};

Terminal.prototype.listeners = function(type) {
  return this.socket.listeners(type);
};

Terminal.prototype.removeListener = function(type, func) {
  this.socket.removeListener(type, func);
  return this;
};

Terminal.prototype.removeAllListeners = function(type) {
  this.socket.removeAllListeners(type);
  return this;
};

Terminal.prototype.once = function(type, func) {
  this.socket.once(type, func);
  return this;
};

/**
 * TTY
 */

Terminal.prototype.resize = function(cols, rows) {
  this.cols = cols;
  this.rows = rows;

  pty.resize(this.fd, cols, rows);
};

Terminal.prototype.destroy = function() {
  var self = this;

  this.socket.writable = false;
  this.write = function() {};
  this.end = this.write;
  this.writable = false;

  // Need to close the read stream so
  // node stops reading a dead file descriptor.
  // Then we can safely SIGINT the shell
  // This may not be necessary, but do it
  // for good measure.
  this.socket.on('close', function() {
    try {
      process.kill(self.pid, 'SIGINT');
    } catch(e) {
      ;
    }
  });

  this.socket.destroy();
};

Terminal.prototype.__defineGetter__('process', function() {
  return pty.process(this.fd, this.pty) || this.file;
});

/**
 * Helpers
 */

function clone(a) {
  var b = {}
    , keys = Object.keys(a || {})
    , i = keys.length;

  while (i--) {
    b[keys[i]] = a[keys[i]];
  }

  return b;
}

function environ(env) {
  var keys = Object.keys(env)
    , i = keys.length
    , pairs = [];

  while (i--) {
    pairs.push(keys[i] + '=' + env[keys[i]]);
  }

  return pairs;
}

/**
 * Expose
 */

exports = Terminal;
exports.Terminal = Terminal;
exports.native = pty;
module.exports = exports;
<commit_msg>always track total number of terminals<commit_after>/**
 * pty.js
 * Copyright (c) 2012, Christopher Jeffrey (MIT License)
 * Binding to the pseudo terminals.
 */

var net = require('net');
var pty = require('../build/Release/pty.node');

/**
 * Terminal
 */

// Example:
//  var term = new Terminal('bash', ['-i'], {
//    name: 'xterm-color',
//    cols: 80,
//    rows: 30,
//    cwd: process.env.HOME,
//    env: { HELLO: 'WORLD' }
//  });

var Terminal = function(file, args, opt) {
  if (!(this instanceof Terminal)) return new Terminal(file, args, opt);

  var self = this
    , env
    , cwd
    , name
    , cols
    , rows
    , term;

  // backward compatibility
  if (typeof args === 'string') {
    opt = {
      name: arguments[1],
      cols: arguments[2],
      rows: arguments[3],
      cwd: process.env.HOME
    };
    args = [];
  }

  // arguments
  args = args || [];
  file = file || 'sh';
  opt = opt || {};

  name = opt.name || 'vt100';
  cols = opt.cols || 80;
  rows = opt.rows || 30;
  env = opt.env || clone(process.env);
  cwd = opt.cwd || process.cwd();

  if (opt.name) env.TERM = name;
  env = environ(env);

  // fork
  term = pty.fork(file, args, env, cwd, cols, rows);
  this.socket = new net.Socket(term.fd);
  this.socket.setEncoding('utf8');
  this.socket.resume();

  // setup
  this.socket.on('error', function(err) {
    self.socket.writable = false;
    self.write = function() {};
    self.end = self.write;
    self.writable = false;

    // EIO, happens when someone closes our child
    // process: the only process in the terminal.
    if (err.code && ~err.code.indexOf('errno 5')) return;

    // throw anything else
    if (self.listeners('error').length < 2) {
      throw err;
    }
  });

  this.socket.on('close', function() {
    Terminal.total--;
  });

  this.pid = term.pid;
  this.fd = term.fd;
  this.pty = term.pty;

  this.file = file;
  this.name = name;
  this.cols = cols;
  this.rows = rows;

  Terminal.total++;

  env = null;
};

Terminal.fork =
Terminal.spawn =
Terminal.createTerminal = function(file, args, opt) {
  return new Terminal(file, args, opt);
};

/**
 * openpty
 */

Terminal.open = function(opt) {
  var self = Object.create(Terminal.prototype)
    , opt = opt || {};

  if (arguments.length > 1) {
    opt = {
      cols: arguments[1],
      rows: arguments[2]
    };
  }

  var cols = opt.cols || 80
    , rows = opt.rows || 30
    , term;

  // open
  term = pty.open(cols, rows);

  self.master = new net.Socket(term.master);
  self.master.setEncoding('utf8');
  self.master.resume();

  self.slave = new net.Socket(term.slave);
  self.slave.setEncoding('utf8');
  self.slave.resume();

  self.socket = self.master;
  self.pid = null;
  self.fd = term.master;
  self.pty = term.pty;

  self.file = process.argv[0] || 'node';
  self.name = process.env.TERM || '';
  self.cols = cols;
  self.rows = rows;

  return self;
};

/**
 * Total
 */

// Keep track of the total
// number of terminals for
// the process.
Terminal.total = 0;

/**
 * Events
 */

// don't inherit from net.Socket in
// order to avoid collisions.

Terminal.prototype.write = function(data) {
  return this.socket.write(data);
};

Terminal.prototype.end = function(data) {
  return this.socket.end(data);
};

Terminal.prototype.pipe = function(dest, options) {
  return this.socket.pipe(dest, options);
};

Terminal.prototype.addListener =
Terminal.prototype.on = function(type, func) {
  this.socket.on(type, func);
  return this;
};

Terminal.prototype.emit = function() {
  return this.socket.emit.apply(this.socket, arguments);
};

Terminal.prototype.listeners = function(type) {
  return this.socket.listeners(type);
};

Terminal.prototype.removeListener = function(type, func) {
  this.socket.removeListener(type, func);
  return this;
};

Terminal.prototype.removeAllListeners = function(type) {
  this.socket.removeAllListeners(type);
  return this;
};

Terminal.prototype.once = function(type, func) {
  this.socket.once(type, func);
  return this;
};

/**
 * TTY
 */

Terminal.prototype.resize = function(cols, rows) {
  this.cols = cols;
  this.rows = rows;

  pty.resize(this.fd, cols, rows);
};

Terminal.prototype.destroy = function() {
  var self = this;

  this.socket.writable = false;
  this.write = function() {};
  this.end = this.write;
  this.writable = false;

  // Need to close the read stream so
  // node stops reading a dead file descriptor.
  // Then we can safely SIGINT the shell
  // This may not be necessary, but do it
  // for good measure.
  this.socket.on('close', function() {
    try {
      process.kill(self.pid, 'SIGINT');
    } catch(e) {
      ;
    }
  });

  this.socket.destroy();
};

Terminal.prototype.__defineGetter__('process', function() {
  return pty.process(this.fd, this.pty) || this.file;
});

/**
 * Helpers
 */

function clone(a) {
  var b = {}
    , keys = Object.keys(a || {})
    , i = keys.length;

  while (i--) {
    b[keys[i]] = a[keys[i]];
  }

  return b;
}

function environ(env) {
  var keys = Object.keys(env)
    , i = keys.length
    , pairs = [];

  while (i--) {
    pairs.push(keys[i] + '=' + env[keys[i]]);
  }

  return pairs;
}

/**
 * Expose
 */

exports = Terminal;
exports.Terminal = Terminal;
exports.native = pty;
module.exports = exports;
<|endoftext|>


<commit_before>/*!
 * Copyright 2016 Yahoo Inc.
 * Licensed under the terms of the MIT license. Please see LICENSE file in the project root for terms.
 * @license
 */

var merge = require('merge');

/**
* Row
* Wrapper for each row in a justified layout.
* Stores relevant values and provides methods for calculating layout of individual rows.
*
* @param {Object} layoutConfig - The same as that passed
* @param {Object} Initialization parameters. The following are all required:
* @param params.top {Number} Top of row, relative to container
* @param params.left {Number} Left side of row relative to container (equal to container left padding)
* @param params.width {Number} Width of row, not including container padding
* @param params.spacing {Number} Horizontal spacing between items
* @param params.targetRowHeight {Number} Layout algorithm will aim for this row height
* @param params.targetRowHeightTolerance {Number} Row heights may vary +/- (`targetRowHeight` x `targetRowHeightTolerance`)
* @param params.edgeCaseMinRowHeight {Number} Absolute minimum row height for edge cases that cannot be resolved within tolerance.
* @param params.edgeCaseMaxRowHeight {Number} Absolute maximum row height for edge cases that cannot be resolved within tolerance.
* @param params.isBreakoutRow {Boolean} Is this row in particular one of those breakout rows? Always false if it's not that kind of photo list
* @param params.widowLayoutStyle {String} If widows are visible, how should they be laid out?
* @constructor
*/
var Row = module.exports = function (params) {

	// Top of row, relative to container
	this.top = params.top;

	// Left side of row relative to container (equal to container left padding)
	this.left = params.left;

	// Width of row, not including container padding
	this.width = params.width;

	// Horizontal spacing between items
	this.spacing = params.spacing;

	// Row height calculation values
	this.targetRowHeight = params.targetRowHeight;
	this.targetRowHeightTolerance = params.targetRowHeightTolerance;
	this.minAspectRatio = this.width / params.targetRowHeight * (1 - params.targetRowHeightTolerance);
	this.maxAspectRatio = this.width / params.targetRowHeight * (1 + params.targetRowHeightTolerance);

	// Edge case row height minimum/maximum
	this.edgeCaseMinRowHeight = params.edgeCaseMinRowHeight;
	this.edgeCaseMaxRowHeight = params.edgeCaseMaxRowHeight;

	// Widow layout direction
	this.widowLayoutStyle = params.widowLayoutStyle;

	// Full width breakout rows
	this.isBreakoutRow = params.isBreakoutRow;

	// Store layout data for each item in row
	this.items = [];

	// Height remains at 0 until it's been calculated
	this.height = 0;

};

Row.prototype = {

	/**
	* Attempt to add a single item to the row.
	* This is the heart of the justified algorithm.
	* This method is direction-agnostic; it deals only with sizes, not positions.
	*
	* If the item fits in the row, without pushing row height beyond min/max tolerance,
	* the item is added and the method returns true.
	*
	* If the item leaves row height too high, there may be room to scale it down and add another item.
	* In this case, the item is added and the method returns true, but the row is incomplete.
	*
	* If the item leaves row height too short, there are too many items to fit within tolerance.
	* The method will either accept or reject the new item, favoring the resulting row height closest to within tolerance.
	* If the item is rejected, left/right padding will be required to fit the row height within tolerance;
	* if the item is accepted, top/bottom cropping will be required to fit the row height within tolerance.
	*
	* @method addItem
	* @param itemData {Object} Item layout data, containing item aspect ratio.
	* @return {Boolean} True if successfully added; false if rejected.
	*/
	addItem: function (itemData) {

		var newItems = this.items.concat(itemData),
			// Calculate aspect ratios for items only; exclude spacing
			rowWidthWithoutSpacing = this.width - (newItems.length - 1) * this.spacing,
			newAspectRatio = newItems.reduce(function (sum, item) {
				return sum + item.aspectRatio;
			}, 0),
			targetAspectRatio = rowWidthWithoutSpacing / this.targetRowHeight,
			previousRowWidthWithoutSpacing,
			previousAspectRatio,
			previousTargetAspectRatio;

		// Handle big full-width breakout photos if we're doing them
		if (this.isBreakoutRow) {
			// Only do it if there's no other items in this row
			if (this.items.length === 0) {
				// Only go full width if this photo is a square or landscape
				if (itemData.aspectRatio >= 1) {
					// Close out the row with a full width photo
					this.items.push(itemData);
					this.completeLayout(rowWidthWithoutSpacing / itemData.aspectRatio, this.widowLayoutStyle);
					return true;
				}
			}
		}

		if (newAspectRatio < this.minAspectRatio) {

			// New aspect ratio is too narrow / scaled row height is too tall.
			// Accept this item and leave row open for more items.

			this.items.push(merge(itemData));
			return true;

		} else if (newAspectRatio > this.maxAspectRatio) {

			// New aspect ratio is too wide / scaled row height will be too short.
			// Accept item if the resulting aspect ratio is closer to target than it would be without the item.
			// NOTE: Any row that falls into this block will require cropping/padding on individual items.

			if (this.items.length === 0) {

				// When there are no existing items, force acceptance of the new item and complete the layout.
				// This is the pano special case.
				this.items.push(merge(itemData));
				this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, this.widowLayoutStyle);
				return true;

			}

			// Calculate width/aspect ratio for row before adding new item
			previousRowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing;
			previousAspectRatio = this.items.reduce(function (sum, item) {
				return sum + item.aspectRatio;
			}, 0);
			previousTargetAspectRatio = previousRowWidthWithoutSpacing / this.targetRowHeight;

			if (Math.abs(newAspectRatio - targetAspectRatio) > Math.abs(previousAspectRatio - previousTargetAspectRatio)) {

				// Row with new item is us farther away from target than row without; complete layout and reject item.
				this.completeLayout(previousRowWidthWithoutSpacing / previousAspectRatio, this.widowLayoutStyle);
				return false;

			} else {

				// Row with new item is us closer to target than row without;
				// accept the new item and complete the row layout.
				this.items.push(merge(itemData));
				this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, this.widowLayoutStyle);
				return true;

			}

		} else {

			// New aspect ratio / scaled row height is within tolerance;
			// accept the new item and complete the row layout.
			this.items.push(merge(itemData));
			this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, this.widowLayoutStyle);
			return true;

		}

	},

	/**
	* Check if a row has completed its layout.
	*
	* @method isLayoutComplete
	* @return {Boolean} True if complete; false if not.
	*/
	isLayoutComplete: function () {
		return this.height > 0;
	},

	/**
	* Set row height and compute item geometry from that height.
	* Will justify items within the row unless instructed not to.
	*
	* @method completeLayout
	* @param newHeight {Number} Set row height to this value.
	* @param widowLayoutStyle {String} How should widows display? Supported: justify | center
	*/
	completeLayout: function (newHeight, widowLayoutStyle) {

		var itemWidthSum = this.left,
			rowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing,
			clampedToNativeRatio,
			roundedHeight,
			clampedHeight,
			errorWidthPerItem,
			roundedCumulativeErrors,
			singleItemGeometry,
			centerOffset;

		// Justify unless explicitly specified otherwise.
		if (typeof widowLayoutStyle === 'undefined' || ['justify', 'center', 'left'].indexOf(widowLayoutStyle) < 0) {
			widowLayoutStyle = 'left';
		}

		// Don't set fractional values in the layout.
		roundedHeight = Math.round(newHeight);

		// Clamp row height to edge case minimum/maximum.
		clampedHeight = Math.max(this.edgeCaseMinRowHeight, Math.min(roundedHeight, this.edgeCaseMaxRowHeight));

		if (roundedHeight !== clampedHeight) {

			// If row height was clamped, the resulting row/item aspect ratio will be off,
			// so force it to fit the width (recalculate aspectRatio to match clamped height).
			// NOTE: this will result in cropping/padding commensurate to the amount of clamping.
			this.height = clampedHeight;
			clampedToNativeRatio = (rowWidthWithoutSpacing / clampedHeight) / (rowWidthWithoutSpacing / roundedHeight);

		} else {

			// If not clamped, leave ratio at 1.0.
			this.height = roundedHeight;
			clampedToNativeRatio = 1.0;

		}

		// Compute item geometry based on newHeight.
		this.items.forEach(function (item) {

			item.top = this.top;
			item.width = Math.round(item.aspectRatio * this.height * clampedToNativeRatio);
			item.height = this.height;

			// Left-to-right.
			// TODO right to left
			// item.left = this.width - itemWidthSum - item.width;
			item.left = itemWidthSum;

			// Increment width.
			itemWidthSum += item.width + this.spacing;

		}, this);

		// If specified, ensure items fill row and distribute error
		// caused by rounding width and height across all items.
		if (widowLayoutStyle === 'justify') {

			itemWidthSum -= (this.spacing + this.left);

			errorWidthPerItem = (itemWidthSum - this.width) / this.items.length;
			roundedCumulativeErrors = this.items.map(function (item, i) {
				return Math.round((i + 1) * errorWidthPerItem);
			});

			if (this.items.length === 1) {

				// For rows with only one item, adjust item width to fill row.
				singleItemGeometry = this.items[0];
				singleItemGeometry.width -= Math.round(errorWidthPerItem);

			} else {

				// For rows with multiple items, adjust item width and shift items to fill the row,
				// while maintaining equal spacing between items in the row.
				this.items.forEach(function (item, i) {
					if (i > 0) {
						item.left -= roundedCumulativeErrors[i - 1];
						item.width -= (roundedCumulativeErrors[i] - roundedCumulativeErrors[i - 1]);
					} else {
						item.width -= roundedCumulativeErrors[i];
					}
				});

			}

		} else if (widowLayoutStyle === 'center') {

			// Center widows
			centerOffset = (this.width - itemWidthSum) / 2;

			this.items.forEach(function (item) {
				item.left += centerOffset + this.spacing;
			}, this);

		}

	},

	/**
	* Force completion of row layout with current items.
	*
	* @method forceComplete
	* @param fitToWidth {Boolean} Stretch current items to fill the row width.
	*                             This will likely result in padding.
	* @param fitToWidth {Number}
	*/
	forceComplete: function (fitToWidth, rowHeight) {

		// TODO Handle fitting to width
		// var rowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing,
		// 	currentAspectRatio = this.items.reduce(function (sum, item) {
		// 		return sum + item.aspectRatio;
		// 	}, 0);

		if (typeof rowHeight === 'number') {

			this.completeLayout(rowHeight, this.widowLayoutStyle);

		} else {

			// Complete using target row height.
			this.completeLayout(this.targetRowHeight, this.widowLayoutStyle);
		}

	},

	/**
	* Return layout data for items within row.
	* Note: returns actual list, not a copy.
	*
	* @method getItems
	* @return Layout data for items within row.
	*/
	getItems: function () {
		return this.items;
	}

};
<commit_msg>Do not apply widowLayoutStyle to full width row<commit_after>/*!
 * Copyright 2016 Yahoo Inc.
 * Licensed under the terms of the MIT license. Please see LICENSE file in the project root for terms.
 * @license
 */

var merge = require('merge');

/**
* Row
* Wrapper for each row in a justified layout.
* Stores relevant values and provides methods for calculating layout of individual rows.
*
* @param {Object} layoutConfig - The same as that passed
* @param {Object} Initialization parameters. The following are all required:
* @param params.top {Number} Top of row, relative to container
* @param params.left {Number} Left side of row relative to container (equal to container left padding)
* @param params.width {Number} Width of row, not including container padding
* @param params.spacing {Number} Horizontal spacing between items
* @param params.targetRowHeight {Number} Layout algorithm will aim for this row height
* @param params.targetRowHeightTolerance {Number} Row heights may vary +/- (`targetRowHeight` x `targetRowHeightTolerance`)
* @param params.edgeCaseMinRowHeight {Number} Absolute minimum row height for edge cases that cannot be resolved within tolerance.
* @param params.edgeCaseMaxRowHeight {Number} Absolute maximum row height for edge cases that cannot be resolved within tolerance.
* @param params.isBreakoutRow {Boolean} Is this row in particular one of those breakout rows? Always false if it's not that kind of photo list
* @param params.widowLayoutStyle {String} If widows are visible, how should they be laid out?
* @constructor
*/
var Row = module.exports = function (params) {

	// Top of row, relative to container
	this.top = params.top;

	// Left side of row relative to container (equal to container left padding)
	this.left = params.left;

	// Width of row, not including container padding
	this.width = params.width;

	// Horizontal spacing between items
	this.spacing = params.spacing;

	// Row height calculation values
	this.targetRowHeight = params.targetRowHeight;
	this.targetRowHeightTolerance = params.targetRowHeightTolerance;
	this.minAspectRatio = this.width / params.targetRowHeight * (1 - params.targetRowHeightTolerance);
	this.maxAspectRatio = this.width / params.targetRowHeight * (1 + params.targetRowHeightTolerance);

	// Edge case row height minimum/maximum
	this.edgeCaseMinRowHeight = params.edgeCaseMinRowHeight;
	this.edgeCaseMaxRowHeight = params.edgeCaseMaxRowHeight;

	// Widow layout direction
	this.widowLayoutStyle = params.widowLayoutStyle;

	// Full width breakout rows
	this.isBreakoutRow = params.isBreakoutRow;

	// Store layout data for each item in row
	this.items = [];

	// Height remains at 0 until it's been calculated
	this.height = 0;

};

Row.prototype = {

	/**
	* Attempt to add a single item to the row.
	* This is the heart of the justified algorithm.
	* This method is direction-agnostic; it deals only with sizes, not positions.
	*
	* If the item fits in the row, without pushing row height beyond min/max tolerance,
	* the item is added and the method returns true.
	*
	* If the item leaves row height too high, there may be room to scale it down and add another item.
	* In this case, the item is added and the method returns true, but the row is incomplete.
	*
	* If the item leaves row height too short, there are too many items to fit within tolerance.
	* The method will either accept or reject the new item, favoring the resulting row height closest to within tolerance.
	* If the item is rejected, left/right padding will be required to fit the row height within tolerance;
	* if the item is accepted, top/bottom cropping will be required to fit the row height within tolerance.
	*
	* @method addItem
	* @param itemData {Object} Item layout data, containing item aspect ratio.
	* @return {Boolean} True if successfully added; false if rejected.
	*/
	addItem: function (itemData) {

		var newItems = this.items.concat(itemData),
			// Calculate aspect ratios for items only; exclude spacing
			rowWidthWithoutSpacing = this.width - (newItems.length - 1) * this.spacing,
			newAspectRatio = newItems.reduce(function (sum, item) {
				return sum + item.aspectRatio;
			}, 0),
			targetAspectRatio = rowWidthWithoutSpacing / this.targetRowHeight,
			previousRowWidthWithoutSpacing,
			previousAspectRatio,
			previousTargetAspectRatio;

		// Handle big full-width breakout photos if we're doing them
		if (this.isBreakoutRow) {
			// Only do it if there's no other items in this row
			if (this.items.length === 0) {
				// Only go full width if this photo is a square or landscape
				if (itemData.aspectRatio >= 1) {
					// Close out the row with a full width photo
					this.items.push(itemData);
					this.completeLayout(rowWidthWithoutSpacing / itemData.aspectRatio, 'justify');
					return true;
				}
			}
		}

		if (newAspectRatio < this.minAspectRatio) {

			// New aspect ratio is too narrow / scaled row height is too tall.
			// Accept this item and leave row open for more items.

			this.items.push(merge(itemData));
			return true;

		} else if (newAspectRatio > this.maxAspectRatio) {

			// New aspect ratio is too wide / scaled row height will be too short.
			// Accept item if the resulting aspect ratio is closer to target than it would be without the item.
			// NOTE: Any row that falls into this block will require cropping/padding on individual items.

			if (this.items.length === 0) {

				// When there are no existing items, force acceptance of the new item and complete the layout.
				// This is the pano special case.
				this.items.push(merge(itemData));
				this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');
				return true;

			}

			// Calculate width/aspect ratio for row before adding new item
			previousRowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing;
			previousAspectRatio = this.items.reduce(function (sum, item) {
				return sum + item.aspectRatio;
			}, 0);
			previousTargetAspectRatio = previousRowWidthWithoutSpacing / this.targetRowHeight;

			if (Math.abs(newAspectRatio - targetAspectRatio) > Math.abs(previousAspectRatio - previousTargetAspectRatio)) {

				// Row with new item is us farther away from target than row without; complete layout and reject item.
				this.completeLayout(previousRowWidthWithoutSpacing / previousAspectRatio, 'justify');
				return false;

			} else {

				// Row with new item is us closer to target than row without;
				// accept the new item and complete the row layout.
				this.items.push(merge(itemData));
				this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');
				return true;

			}

		} else {

			// New aspect ratio / scaled row height is within tolerance;
			// accept the new item and complete the row layout.
			this.items.push(merge(itemData));
			this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');
			return true;

		}

	},

	/**
	* Check if a row has completed its layout.
	*
	* @method isLayoutComplete
	* @return {Boolean} True if complete; false if not.
	*/
	isLayoutComplete: function () {
		return this.height > 0;
	},

	/**
	* Set row height and compute item geometry from that height.
	* Will justify items within the row unless instructed not to.
	*
	* @method completeLayout
	* @param newHeight {Number} Set row height to this value.
	* @param widowLayoutStyle {String} How should widows display? Supported: left | justify | center
	*/
	completeLayout: function (newHeight, widowLayoutStyle) {

		var itemWidthSum = this.left,
			rowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing,
			clampedToNativeRatio,
			roundedHeight,
			clampedHeight,
			errorWidthPerItem,
			roundedCumulativeErrors,
			singleItemGeometry,
			centerOffset;

		// Justify unless explicitly specified otherwise.
		if (typeof widowLayoutStyle === 'undefined' || ['justify', 'center', 'left'].indexOf(widowLayoutStyle) < 0) {
			widowLayoutStyle = 'left';
		}

		// Don't set fractional values in the layout.
		roundedHeight = Math.round(newHeight);

		// Clamp row height to edge case minimum/maximum.
		clampedHeight = Math.max(this.edgeCaseMinRowHeight, Math.min(roundedHeight, this.edgeCaseMaxRowHeight));

		if (roundedHeight !== clampedHeight) {

			// If row height was clamped, the resulting row/item aspect ratio will be off,
			// so force it to fit the width (recalculate aspectRatio to match clamped height).
			// NOTE: this will result in cropping/padding commensurate to the amount of clamping.
			this.height = clampedHeight;
			clampedToNativeRatio = (rowWidthWithoutSpacing / clampedHeight) / (rowWidthWithoutSpacing / roundedHeight);

		} else {

			// If not clamped, leave ratio at 1.0.
			this.height = roundedHeight;
			clampedToNativeRatio = 1.0;

		}

		// Compute item geometry based on newHeight.
		this.items.forEach(function (item) {

			item.top = this.top;
			item.width = Math.round(item.aspectRatio * this.height * clampedToNativeRatio);
			item.height = this.height;

			// Left-to-right.
			// TODO right to left
			// item.left = this.width - itemWidthSum - item.width;
			item.left = itemWidthSum;

			// Increment width.
			itemWidthSum += item.width + this.spacing;

		}, this);

		// If specified, ensure items fill row and distribute error
		// caused by rounding width and height across all items.
		if (widowLayoutStyle === 'justify') {

			itemWidthSum -= (this.spacing + this.left);

			errorWidthPerItem = (itemWidthSum - this.width) / this.items.length;
			roundedCumulativeErrors = this.items.map(function (item, i) {
				return Math.round((i + 1) * errorWidthPerItem);
			});

			if (this.items.length === 1) {

				// For rows with only one item, adjust item width to fill row.
				singleItemGeometry = this.items[0];
				singleItemGeometry.width -= Math.round(errorWidthPerItem);

			} else {

				// For rows with multiple items, adjust item width and shift items to fill the row,
				// while maintaining equal spacing between items in the row.
				this.items.forEach(function (item, i) {
					if (i > 0) {
						item.left -= roundedCumulativeErrors[i - 1];
						item.width -= (roundedCumulativeErrors[i] - roundedCumulativeErrors[i - 1]);
					} else {
						item.width -= roundedCumulativeErrors[i];
					}
				});

			}

		} else if (widowLayoutStyle === 'center') {

			// Center widows
			centerOffset = (this.width - itemWidthSum) / 2;

			this.items.forEach(function (item) {
				item.left += centerOffset + this.spacing;
			}, this);

		}

	},

	/**
	* Force completion of row layout with current items.
	*
	* @method forceComplete
	* @param fitToWidth {Boolean} Stretch current items to fill the row width.
	*                             This will likely result in padding.
	* @param fitToWidth {Number}
	*/
	forceComplete: function (fitToWidth, rowHeight) {

		// TODO Handle fitting to width
		// var rowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing,
		// 	currentAspectRatio = this.items.reduce(function (sum, item) {
		// 		return sum + item.aspectRatio;
		// 	}, 0);

		if (typeof rowHeight === 'number') {

			this.completeLayout(rowHeight, this.widowLayoutStyle);

		} else {

			// Complete using target row height.
			this.completeLayout(this.targetRowHeight, this.widowLayoutStyle);
		}

	},

	/**
	* Return layout data for items within row.
	* Note: returns actual list, not a copy.
	*
	* @method getItems
	* @return Layout data for items within row.
	*/
	getItems: function () {
		return this.items;
	}

};
<|endoftext|>


<commit_before>'use strict'

const debug = require('debug')('skiff.rpc')
const once = require('once')
const uuid = require('uuid').v4
const timers = require('timers')

module.exports = function createRPC (node, network, replies, emitter, defaults) {
  return function rpc (options, callback) {
    debug('%s: rpc to: %s, action: %s, params: %j', node.id, options.to, options.action, options.params)
    const term = node.term()
    const done = once(callback)
    const id = uuid()

    const timeoutMS = options.timeout || defaults.rpcTimeoutMS
    const timeout = timers.setTimeout(onTimeout, timeoutMS)
    const started = Date.now()

    network.write({
      from: node.id,
      id,
      type: 'request',
      to: options.to,
      action: options.action,
      params: options.params
    }, err => {
      if (err) {
        cancel()
        done(err)
      } else {
        emitter.emit('message sent')
        emitter.emit('rpc sent', options.action)
      }
    })
    replies.on('data', onReplyData)

    function onReplyData (message) {
      debug('%s: reply data: %j', node.id, message)
      if (!message.fake) {
        timers.setImmediate(() => emitter.emit('rpc latency', Date.now() - started))
      }

      const accept = (
        message.type === 'reply' &&
        message.from === options.to &&
        message.id === id)

      if (node.term() > term) {
        onOutdatedTerm()
      } else if (accept) {
        debug('%s: this is a reply I was expecting: %j', node.id, message)
        cancel()
        const error = message.error
        done(error, !error && message)
      }
    }

    function onTimeout () {
      debug('RPC timeout')
      cancel()
      done(Object.assign(new Error(`timeout RPC to ${options.to}`), { code: 'ETIMEOUT' }))
    }

    function onOutdatedTerm () {
      debug('Outdated term')
      cancel()
      done(Object.assign(new Error('outdated term'), { code: 'EOUTDATEDTERM' }))
    }

    function cancel () {
      replies.removeListener('data', onReplyData)
      timers.clearTimeout(timeout)
    }
  }
}
<commit_msg>more explicit timeout error message<commit_after>'use strict'

const debug = require('debug')('skiff.rpc')
const once = require('once')
const uuid = require('uuid').v4
const timers = require('timers')

module.exports = function createRPC (node, network, replies, emitter, defaults) {
  return function rpc (options, callback) {
    debug('%s: rpc to: %s, action: %s, params: %j', node.id, options.to, options.action, options.params)
    const term = node.term()
    const done = once(callback)
    const id = uuid()

    const timeoutMS = options.timeout || defaults.rpcTimeoutMS
    const timeout = timers.setTimeout(onTimeout, timeoutMS)
    const started = Date.now()

    network.write({
      from: node.id,
      id,
      type: 'request',
      to: options.to,
      action: options.action,
      params: options.params
    }, err => {
      if (err) {
        cancel()
        done(err)
      } else {
        emitter.emit('message sent')
        emitter.emit('rpc sent', options.action)
      }
    })
    replies.on('data', onReplyData)

    function onReplyData (message) {
      debug('%s: reply data: %j', node.id, message)
      if (!message.fake) {
        timers.setImmediate(() => emitter.emit('rpc latency', Date.now() - started))
      }

      const accept = (
        message.type === 'reply' &&
        message.from === options.to &&
        message.id === id)

      if (node.term() > term) {
        onOutdatedTerm()
      } else if (accept) {
        debug('%s: this is a reply I was expecting: %j', node.id, message)
        cancel()
        const error = message.error
        done(error, !error && message)
      }
    }

    function onTimeout () {
      debug('RPC timeout')
      cancel()
      done(Object.assign(new Error(`timeout RPC to ${options.to}, action = ${options.action}`), { code: 'ETIMEOUT' }))
    }

    function onOutdatedTerm () {
      debug('Outdated term')
      cancel()
      done(Object.assign(new Error('outdated term'), { code: 'EOUTDATEDTERM' }))
    }

    function cancel () {
      replies.removeListener('data', onReplyData)
      timers.clearTimeout(timeout)
    }
  }
}
<|endoftext|>


<commit_before>'use strict'

const minimist = require('minimist');
const Parser = require('tap-parser');
const moment = require('moment');
const co = require('co');
const github = require('./github');

module.exports = (_argv, doExit) => {
  const opts = getOptions(_argv);

  return new Promise((resolve, reject) => {
    var p = new Parser({passes: true}, function (results) {
      const tests = prepareTests(results);
      co(function *() {
        for (const repo in tests) yield processRepo(repo, tests[repo]);
      }).then(resolve, reject);
    });

    process.stdin.pipe(p);
  });

  function *processRepo(repo, repoTests) {
    const existingIssues = yield github.issue.list(repo, opts);
    for (const test of repoTests) {
      const ok = test.ok;
      const {rule, issue, message, severity} = test.diag;
      const existing = existingIssues.find(({title}) =>
        title.indexOf(issue.title) >= 0 && title.indexOf(rule) >= 0);

      if (existing) {
        if (existing.state == 'open') {
          if (ok) {
            console.log('ok (closing...):', rule, repo);
            yield github.issue.comment(repo, existing, issue.comments.close, opts);
            yield github.issue.close(repo, existing, opts);
          } else {
            const passedDays = moment().diff(existing.updated_at, 'days', true);
            if (passedDays >= opts.remind) {
              console.log('not ok (reminding...):', rule, repo);
              yield github.issue.comment(repo, existing, issue.comments.update, opts);
            } else {
              console.log('not ok (recent):', rule, repo);
            }
          }
        } else { // issue state == 'closed'
          if (ok) {
            console.log('ok (resolved):', rule, repo);
          } else {
            console.log('not ok (re-opening...):', rule, repo);
            yield github.issue.comment(repo, existing, issue.comments.reopen, opts);
            yield github.issue.reopen(repo, existing, opts);
          }
        }
      } else {
        if (ok) {
          console.log('ok (no issue):', rule, repo);
        } else {
          yield github.issue.create(repo, {
            title: `[${rule}] ${issue.title}`,
            body: `${issue.comments.create}`,
            labels: [opts.label]
          }, opts)
          console.log('not ok (creating...):', rule, repo);
        }
      }
    }
  }
};

function getOptions(_argv) {
  const argv = minimist(_argv);
  const opts = {
    label: argv.l || argv.label
  };
  if (!opts.label) {
    console.error('issue label is required');
    process.exit(2);
  }

  const user = argv.u || argv.user;
  const pass = argv.p || argv.pass;
  if (user || pass) opts.auth = {user, pass};

  opts.remind = argv.r || argv.remind || 7;

  return opts;
}

function prepareTests(results) {
  const testCases = results.failures.concat(results.passes);
  // console.log(testCases);
  const tests = {};
  for (const test of testCases) {
    const repo = test.diag.repo;
    const repoTests = tests[repo] = tests[repo] || [];
    repoTests.push(test);
  }
  return tests;
}
<commit_msg>feat: log issue URLs<commit_after>'use strict'

const minimist = require('minimist');
const Parser = require('tap-parser');
const moment = require('moment');
const co = require('co');
const github = require('./github');

module.exports = (_argv, doExit) => {
  const opts = getOptions(_argv);

  return new Promise((resolve, reject) => {
    var p = new Parser({passes: true}, function (results) {
      const tests = prepareTests(results);
      co(function *() {
        for (const repo in tests) yield processRepo(repo, tests[repo]);
      }).then(resolve, reject);
    });

    process.stdin.pipe(p);
  });

  function *processRepo(repo, repoTests) {
    const existingIssues = yield github.issue.list(repo, opts);
    for (const test of repoTests) {
      const ok = test.ok;
      const {rule, issue, message, severity} = test.diag;
      const existing = existingIssues.find(({title}) =>
        title.indexOf(issue.title) >= 0 && title.indexOf(rule) >= 0);

      if (existing) {
        if (existing.state == 'open') {
          if (ok) {
            console.log('ok (closing...):', rule, existing.html_url);
            yield github.issue.comment(repo, existing, issue.comments.close, opts);
            yield github.issue.close(repo, existing, opts);
          } else {
            const passedDays = moment().diff(existing.updated_at, 'days', true);
            if (passedDays >= opts.remind) {
              console.log('not ok (reminding...):', rule, existing.html_url);
              yield github.issue.comment(repo, existing, issue.comments.update, opts);
            } else {
              console.log('not ok (recent):', rule, existing.html_url);
            }
          }
        } else { // issue state == 'closed'
          if (ok) {
            console.log('ok (resolved):', rule, existing.html_url);
          } else {
            console.log('not ok (re-opening...):', rule, existing.html_url);
            yield github.issue.comment(repo, existing, issue.comments.reopen, opts);
            yield github.issue.reopen(repo, existing, opts);
          }
        }
      } else {
        if (ok) {
          console.log('ok (no issue):', rule, repo);
        } else {
          const newIssue = yield github.issue.create(repo, {
            title: `[${rule}] ${issue.title}`,
            body: `${issue.comments.create}`,
            labels: [opts.label]
          }, opts)
          console.log('not ok (creating...):', rule, newIssue.html_url);
        }
      }
    }
  }
};

function getOptions(_argv) {
  const argv = minimist(_argv);
  const opts = {
    label: argv.l || argv.label
  };
  if (!opts.label) {
    console.error('issue label is required');
    process.exit(2);
  }

  const user = argv.u || argv.user;
  const pass = argv.p || argv.pass;
  if (user || pass) opts.auth = {user, pass};

  opts.remind = argv.r || argv.remind || 7;

  return opts;
}

function prepareTests(results) {
  const testCases = results.failures.concat(results.passes);
  // console.log(testCases);
  const tests = {};
  for (const test of testCases) {
    const repo = test.diag.repo;
    const repoTests = tests[repo] = tests[repo] || [];
    repoTests.push(test);
  }
  return tests;
}
<|endoftext|>


<commit_before>"use strict";

var async = require("async");
var childProcess = require("child_process");
var filter = require("./filter");
var EventEmitter = require("./EventEmitter.js");

var defaultTestCmd = "npm test";

function sanitizeConfig(config) {
    config.reporter = config.reporter || Function.prototype;
    config.testCmd = config.testCmd || defaultTestCmd;
    config.exclude = config.exclude && config.exclude.split(",").map(function (name) {
        return name.trim();
    }) || [];
}

function run(config, done) {
    var emitter = new EventEmitter();
    var testCmd;
    var reporter;
    var cwd;

    function exec(cmd, cb) {
        childProcess.exec(cmd, { encoding: "utf8", cwd: config.cwd }, cb);
    }

    function finish(err) {
        emitter.removeAllListeners();
        done(err || null);
    }

    sanitizeConfig(config);

    reporter = config.reporter;
    cwd = config.cwd;
    testCmd = config.testCmd;

    if (typeof cwd !== "string") {
        throw new Error("Cannot run updtr: cwd missing");
    }

    reporter(emitter);

    emitter.emit("init", {
        cwd: config.cwd
    });
    exec("npm outdated --json --long --depth=0", function (err, stdout, stderr) {
        var outdated;
        var infos;
        var tasks;
        var modulesMissing;

        function createTask(info, index) {
            index++;

            return function (done) {
                var event = {
                    current: index,
                    total: tasks.length,
                    info: info,
                    testCmd: testCmd,
                    installCmd: installCmd = "npm i" + (config.registry ? (" --registry " + config.registry) : "")
                };
                var testStdout;
                var installCmd = event.installCmd + " " + info.name + "@" + info.updateTo + " " + info.saveCmd;

                if (config.saveExact) {
                    installCmd += " --save-exact";
                }

                emitter.emit("updating", event);

                async.series({
                    deleteOldVersion: async.apply(exec, "npm remove " + info.name + " " + info.saveCmd),
                    installNewVersion: async.apply(exec, installCmd),
                    emitTestingEvent: function (done) {
                        emitter.emit("testing", event);
                        setImmediate(done);
                    },
                    runTests: function runTests(callback) {
                        exec(testCmd, function (err, stdout, stderr) {
                            if (err) {
                                testStdout = stdout;
                                callback(err);
                                return;
                            }
                            callback(null, stdout, stderr);
                        });
                    }
                }, function (err) {
                    if (err) {
                        emitter.emit("rollback", event);
                        exec(event.installCmd + " " + info.name + "@" + info.current + " " + info.saveCmd + (config.saveExact ? " --save-exact" : ""), function (err) {
                            if (err) {
                                finish(err);
                                return;
                            }
                            emitter.emit("rollbackDone", event);
                            if (config.testStdout) {
                                event.testStdout = testStdout;
                                emitter.emit("testStdout", event);
                            }
                            done();
                        });
                        return;
                    }
                    emitter.emit("updatingDone", event);
                    done();
                });
            };
        }

        if (err) {
            finish(err);
            return;
        }

        if (!stdout) {
            emitter.emit("noop");
            finish();
            return;
        }

        outdated = JSON.parse(stdout);
        infos = Object.keys(outdated)
            .map(function (moduleName) {
                var info = outdated[moduleName];

                info.name = moduleName;
                info.saveCmd = info.type === "devDependencies" ? "--save-dev" : "--save";
                info.updateTo = config.wanted ? info.wanted : info.latest;

                return info;
            })
            .filter(filter(config));

        modulesMissing = infos.some(function (info) {
            return !info.current;
        });

        if (modulesMissing) {
            emitter.emit("modulesMissing");
            finish();
            return;
        }

        if (infos.length === 0) {
            emitter.emit("noop");
            finish();
            return;
        }

        emitter.emit("outdated", {
            infos: infos,
            total: infos.length
        });

        tasks = infos
            .map(function (info, index, outdatedModules) {
                return createTask(info, index, outdatedModules);
            });

        async.series(tasks, function (err) {
            if (err) {
                finish(err);
                return;
            }
            emitter.emit("finished");
            finish();
        });
    });
}

module.exports = run;
<commit_msg>Fix maxBuffer errors for verbose test outputs ala @meaku<commit_after>"use strict";

var async = require("async");
var childProcess = require("child_process");
var filter = require("./filter");
var EventEmitter = require("./EventEmitter.js");

var defaultTestCmd = "npm test";

function sanitizeConfig(config) {
    config.reporter = config.reporter || Function.prototype;
    config.testCmd = config.testCmd || defaultTestCmd;
    config.exclude = config.exclude && config.exclude.split(",").map(function (name) {
        return name.trim();
    }) || [];
}

function run(config, done) {
    var emitter = new EventEmitter();
    var testCmd;
    var reporter;
    var cwd;

    function exec(cmd, cb) {
        childProcess.exec(cmd, { maxBuffer: Infinity, encoding: "utf8", cwd: config.cwd }, cb);
    }

    function finish(err) {
        emitter.removeAllListeners();
        done(err || null);
    }

    sanitizeConfig(config);

    reporter = config.reporter;
    cwd = config.cwd;
    testCmd = config.testCmd;

    if (typeof cwd !== "string") {
        throw new Error("Cannot run updtr: cwd missing");
    }

    reporter(emitter);

    emitter.emit("init", {
        cwd: config.cwd
    });
    exec("npm outdated --json --long --depth=0", function (err, stdout, stderr) {
        var outdated;
        var infos;
        var tasks;
        var modulesMissing;

        function createTask(info, index) {
            index++;

            return function (done) {
                var event = {
                    current: index,
                    total: tasks.length,
                    info: info,
                    testCmd: testCmd,
                    installCmd: installCmd = "npm i" + (config.registry ? (" --registry " + config.registry) : "")
                };
                var testStdout;
                var installCmd = event.installCmd + " " + info.name + "@" + info.updateTo + " " + info.saveCmd;

                if (config.saveExact) {
                    installCmd += " --save-exact";
                }

                emitter.emit("updating", event);

                async.series({
                    deleteOldVersion: async.apply(exec, "npm remove " + info.name + " " + info.saveCmd),
                    installNewVersion: async.apply(exec, installCmd),
                    emitTestingEvent: function (done) {
                        emitter.emit("testing", event);
                        setImmediate(done);
                    },
                    runTests: function runTests(callback) {
                        exec(testCmd, function (err, stdout, stderr) {
                            if (err) {
                                testStdout = stdout;
                                callback(err);
                                return;
                            }
                            callback(null, stdout, stderr);
                        });
                    }
                }, function (err) {
                    if (err) {
                        emitter.emit("rollback", event);
                        exec(event.installCmd + " " + info.name + "@" + info.current + " " + info.saveCmd + (config.saveExact ? " --save-exact" : ""), function (err) {
                            if (err) {
                                finish(err);
                                return;
                            }
                            emitter.emit("rollbackDone", event);
                            if (config.testStdout) {
                                event.testStdout = testStdout;
                                emitter.emit("testStdout", event);
                            }
                            done();
                        });
                        return;
                    }
                    emitter.emit("updatingDone", event);
                    done();
                });
            };
        }

        if (err) {
            finish(err);
            return;
        }

        if (!stdout) {
            emitter.emit("noop");
            finish();
            return;
        }

        outdated = JSON.parse(stdout);
        infos = Object.keys(outdated)
            .map(function (moduleName) {
                var info = outdated[moduleName];

                info.name = moduleName;
                info.saveCmd = info.type === "devDependencies" ? "--save-dev" : "--save";
                info.updateTo = config.wanted ? info.wanted : info.latest;

                return info;
            })
            .filter(filter(config));

        modulesMissing = infos.some(function (info) {
            return !info.current;
        });

        if (modulesMissing) {
            emitter.emit("modulesMissing");
            finish();
            return;
        }

        if (infos.length === 0) {
            emitter.emit("noop");
            finish();
            return;
        }

        emitter.emit("outdated", {
            infos: infos,
            total: infos.length
        });

        tasks = infos
            .map(function (info, index, outdatedModules) {
                return createTask(info, index, outdatedModules);
            });

        async.series(tasks, function (err) {
            if (err) {
                finish(err);
                return;
            }
            emitter.emit("finished");
            finish();
        });
    });
}

module.exports = run;
<|endoftext|>


<commit_before>module.exports = function (db) {
	db.set = set;
	return db;
}

function set(path, obj, cb) {
	var objs = [];
  putObjBatch(path, obj, objs);

  this.batch(objs, function (err) {
		cb(err);
	});
}

function putObjBatch(path, obj, objs) {
	var keyPath = '';
	if (path !== '/' && path.length > 1) {
		keyPath = path.substr(1) + '/';
	}

	for (var objKey in obj) {
		var objVal = obj[objKey];

		if (typeof objVal === 'object') {
			if (keyPath !== '' && keyPath.length > 1) {
				putObjBatch('/' + keyPath + objKey, objVal, objs);
			} else {
				putObjBatch('/' + objKey, objVal, objs);
			}

		} else {
			objs.push({ type: 'put', key: keyPath + objKey, value: objVal });
		}
  }
}<commit_msg>Put the missing semicolon at the end of exports declaration<commit_after>module.exports = function (db) {
	db.set = set;
	return db;
};

function set(path, obj, cb) {
	var objs = [];
  putObjBatch(path, obj, objs);

  this.batch(objs, function (err) {
		cb(err);
	});
}

function putObjBatch(path, obj, objs) {
	var keyPath = '';
	if (path !== '/' && path.length > 1) {
		keyPath = path.substr(1) + '/';
	}

	for (var objKey in obj) {
		var objVal = obj[objKey];

		if (typeof objVal === 'object') {
			if (keyPath !== '' && keyPath.length > 1) {
				putObjBatch('/' + keyPath + objKey, objVal, objs);
			} else {
				putObjBatch('/' + objKey, objVal, objs);
			}

		} else {
			objs.push({ type: 'put', key: keyPath + objKey, value: objVal });
		}
  }
}<|endoftext|>


<commit_before>var path = require('path');
var Promise = require('bluebird');
var utils = require(path.resolve(__dirname + '/utils'));
var client = null;
var LAST_DECAY_KEY = '_last_decay';
var LIFETIME_KEY = '_t';
var HI_PASS_FILTER = 0.0001;

var Set = function(key) {
  if (!this instanceof Set) {
    return new Set(key);
  }
  this.key = key;
};

Set.prototype.fetch = function(options) {
  return new Promise(function(resolve, reject) {
    var o = options || {};
    var limit = o.limit;
    var self = this;

    var run = function() {
      if (o.bin) {
        client.zscore([self.key, o.bin], function(e, res) {
          if (e) return reject(e);
          resolve(res);
        });
      } else {
        self.fetchRaw(o)
          .then(resolve)
          .catch(reject);
      }
    };

    if (o.scrub && o.decay) {
      this.scrubAndDecay(o)
        .then(run)
        .catch(reject);
    } else if (o.scrub) {
      this.scrub()
        .then(run)
        .catch(reject);
    } else if (o.decay) {
      this.decay(o)
        .then(run)
        .catch(reject);
    } else {
      run();
    }
  }.bind(this));
};

Set.prototype.scrubAndDecay = function(options) {
  return new Promise(function(resolve, reject) {
    var o = options;
    this.scrub()
      .bind(this)
      .then(this.decay)
      .then(resolve)
      .catch(reject);
  }.bind(this));
};

Set.prototype.execDecay = function(_delta, _set) {
  return new Promise(function(resolve, reject) {
    var delta = _delta;
    var set = _set;

    this.getLifetime()
      .bind(this)
      .then(function onLifetimeKey(lifetime) {
        var rate = 1 / parseInt(lifetime, 10);
        var multi = client.multi();
        var v = 0;

        for (var i=0; i<set.length; i++) {
          v = set[i].score * Math.exp(-delta * rate);
          multi.zadd(this.key, v, set[i].name);
        }

        multi.exec(function(e, replies) {
          this.updateDecayDate(new Date().getTime())
            .then(function onUpdateDecayDate(res) {
              resolve(res);
            })
            .catch(reject)
        }.bind(this));
      })
      .catch(reject);
  }.bind(this));
};

Set.prototype.decay = function(options) {
  return new Promise(function decayPromise(resolve, reject) {
    var o = options || {};

    this.getLastDecayDate()
      .bind(this)
      .then(function onLastDecayDateComplete(lastDecayDate) {
        var nextDecayDate = o.date || new Date().getTime();
        var delta = nextDecayDate - lastDecayDate;

        this.fetchRaw(o)
          .bind(this)
          .then(function onFetchRaw(set) {
            this.execDecay(delta, set)
              .bind(this)
              .then(resolve)
              .catch(reject);
          })
          .catch(function onFetchRawError(e) {
            reject(e);
          });
      });
  }.bind(this));
};

Set.prototype.getLifetime = function(cb) {
  return client.zscoreAsync([this.key, LIFETIME_KEY]);
};

Set.prototype.scrub = function() {
  return client.zremrangebyscoreAsync([this.key, '-inf', HI_PASS_FILTER]);
};

Set.prototype.getLastDecayDate = function(cb) {
  return new Promise(function getLastDecayPromise(resolve, reject) {
    return client.zscoreAsync([this.key, LAST_DECAY_KEY])
      .bind(this)
      .then(function onGetLastDecayDateComplete(date) {
        resolve(parseInt(date, 10));
      })
      .catch(reject);
  }.bind(this))
};

Set.prototype.fetchRaw = function(options) {
  return new Promise(function fetchRawPromise(resolve, reject) {
    var o = options || {};

    var limit = o.limit || -1;
    var bufferedLimit = limit;
    var self = this;

    if (limit > 0) {
      bufferedLimit += (this.specialKeys().length - 1);
    }

    client.zrevrangeAsync(this.key, 0, bufferedLimit, 'withscores')
      .bind(this)
      .then(function onComplete(set) {
        set = utils.filter(set, this.specialKeysObj());
        resolve(set);
      })
      .catch(reject);
  }.bind(this));
};

Set.prototype.updateDecayDate = function(date, key) {
  return client.zaddAsync([this.key, date, LAST_DECAY_KEY]);
};

/**
Increments a bin by n amount.

@param {Object} options {
  date: <last increment date>
  by: <increment by n>
  bin: <bin to increment>
}
@return Promise
*/
Set.prototype.incr = function(options) {
  return new Promise(function incrPromise(resolve, reject) {
    var o = options || {};
    var date = o.date || new Date().getTime();

    this.isValidIncrDate(date)
      .bind(this)
      .then(client.zincrbyAsync([this.key, o.by, o.bin]))
      .then(resolve)
      .catch(reject);
  }.bind(this));
};

Set.prototype.isValidIncrDate = function(date) {
  return new Promise(function isValidIncrDatePromise(resolve, reject) {
    var _date = date;
    this.getLastDecayDate()
      .bind(this)
      .then(function onGetLastDecayDateComplete(lastDecayDate) {
        if (date < lastDecayDate) {
          return reject(new Error('Invalid increment date specified!'));
        }

        resolve();
      })
      .catch(reject);
  }.bind(this));
};

Set.prototype.specialKeys = function() {
  return [LIFETIME_KEY, LAST_DECAY_KEY];
};

Set.prototype.specialKeysObj = function() {
  return {'_t': 1, '_last_decay': 1};
};

Set.prototype.createLifetimeKey = function(date, cb) {
  return client.zaddAsync([this.key, date, LIFETIME_KEY]);
};

/**
# @param float opts[time] : mean lifetime of an observation (secs).
# @param datetime opts[date] : a manual date to start decaying from.
*/
exports.create = function(options) {
  return new Promise(function createPromise(resolve, reject) {
    var o = options;

    if (typeof o !== typeof {}) return reject(new Error('Invalid options!'));

    if (!o.time) return reject(new Error('Invalid mean lifetime specified!'));

    if (!o.key) return reject(new Error('Invalid set key specified'));

    var date = o.date || new Date().getTime();
    var set = new Set(o.key);

    set.updateDecayDate(date)
      .then(function onUpdateDecayDate() {
        set.createLifetimeKey(o.time)
          .then(resolve)
          .catch(reject);
      })
      .catch(reject);
  });
};

exports.get = function(name) {
  return new Set(name);
};

exports.setRedisClient = function(_client) {
  client = _client;
};

exports.Set = Set;<commit_msg>Fixed variable ref error<commit_after>var path = require('path');
var Promise = require('bluebird');
var utils = require(path.resolve(__dirname + '/utils'));
var client = null;
var LAST_DECAY_KEY = '_last_decay';
var LIFETIME_KEY = '_t';
var HI_PASS_FILTER = 0.0001;

var Set = function(key) {
  if (!this instanceof Set) {
    return new Set(key);
  }
  this.key = key;
};

Set.prototype.fetch = function(options) {
  return new Promise(function(resolve, reject) {
    var o = options || {};
    var limit = o.limit;
    var self = this;

    var run = function() {
      if (o.bin) {
        client.zscore([self.key, o.bin], function(e, res) {
          if (e) return reject(e);
          resolve(res);
        });
      } else {
        self.fetchRaw(o)
          .then(resolve)
          .catch(reject);
      }
    };

    if (o.scrub && o.decay) {
      this.scrubAndDecay(o)
        .then(run)
        .catch(reject);
    } else if (o.scrub) {
      this.scrub()
        .then(run)
        .catch(reject);
    } else if (o.decay) {
      this.decay(o)
        .then(run)
        .catch(reject);
    } else {
      run();
    }
  }.bind(this));
};

Set.prototype.scrubAndDecay = function(options) {
  return new Promise(function(resolve, reject) {
    var o = options;
    this.scrub()
      .bind(this)
      .then(this.decay)
      .then(resolve)
      .catch(reject);
  }.bind(this));
};

Set.prototype.execDecay = function(_delta, _set) {
  return new Promise(function(resolve, reject) {
    var delta = _delta;
    var set = _set;

    this.getLifetime()
      .bind(this)
      .then(function onLifetimeKey(lifetime) {
        var rate = 1 / parseInt(lifetime, 10);
        var multi = client.multi();
        var v = 0;

        for (var i=0; i<set.length; i++) {
          v = set[i].score * Math.exp(-delta * rate);
          multi.zadd(this.key, v, set[i].item);
        }

        multi.exec(function(e, replies) {
          this.updateDecayDate(new Date().getTime())
            .then(resolve)
            .catch(reject)
        }.bind(this));
      })
      .catch(reject);
  }.bind(this));
};

Set.prototype.decay = function(options) {
  return new Promise(function decayPromise(resolve, reject) {
    var o = options || {};

    this.getLastDecayDate()
      .bind(this)
      .then(function onLastDecayDateComplete(lastDecayDate) {
        var nextDecayDate = o.date || new Date().getTime();
        var delta = nextDecayDate - lastDecayDate;

        this.fetchRaw(o)
          .bind(this)
          .then(function onFetchRaw(set) {
            this.execDecay(delta, set)
              .bind(this)
              .then(resolve)
              .catch(reject);
          })
          .catch(function onFetchRawError(e) {
            reject(e);
          });
      });
  }.bind(this));
};

Set.prototype.getLifetime = function(cb) {
  return client.zscoreAsync([this.key, LIFETIME_KEY]);
};

Set.prototype.scrub = function() {
  return client.zremrangebyscoreAsync([this.key, '-inf', HI_PASS_FILTER]);
};

Set.prototype.getLastDecayDate = function(cb) {
  return new Promise(function getLastDecayPromise(resolve, reject) {
    return client.zscoreAsync([this.key, LAST_DECAY_KEY])
      .bind(this)
      .then(function onGetLastDecayDateComplete(date) {
        resolve(parseInt(date, 10));
      })
      .catch(reject);
  }.bind(this))
};

Set.prototype.fetchRaw = function(options) {
  return new Promise(function fetchRawPromise(resolve, reject) {
    var o = options || {};

    var limit = o.limit || -1;
    var bufferedLimit = limit;
    var self = this;

    if (limit > 0) {
      bufferedLimit += (this.specialKeys().length - 1);
    }

    client.zrevrangeAsync(this.key, 0, bufferedLimit, 'withscores')
      .bind(this)
      .then(function onComplete(set) {
        set = utils.filter(set, this.specialKeysObj());
        resolve(set);
      })
      .catch(reject);
  }.bind(this));
};

Set.prototype.updateDecayDate = function(date, key) {
  return client.zaddAsync([this.key, date, LAST_DECAY_KEY]);
};

/**
Increments a bin by n amount.

@param {Object} options {
  date: <last increment date>
  by: <increment by n>
  bin: <bin to increment>
}
@return Promise
*/
Set.prototype.incr = function(options) {
  return new Promise(function incrPromise(resolve, reject) {
    var o = options || {};
    var date = o.date || new Date().getTime();

    this.isValidIncrDate(date)
      .bind(this)
      .then(client.zincrbyAsync([this.key, o.by, o.bin]))
      .then(resolve)
      .catch(reject);
  }.bind(this));
};

Set.prototype.isValidIncrDate = function(date) {
  return new Promise(function isValidIncrDatePromise(resolve, reject) {
    var _date = date;
    this.getLastDecayDate()
      .bind(this)
      .then(function onGetLastDecayDateComplete(lastDecayDate) {
        if (date < lastDecayDate) {
          return reject(new Error('Invalid increment date specified!'));
        }

        resolve();
      })
      .catch(reject);
  }.bind(this));
};

Set.prototype.specialKeys = function() {
  return [LIFETIME_KEY, LAST_DECAY_KEY];
};

Set.prototype.specialKeysObj = function() {
  return {'_t': 1, '_last_decay': 1};
};

Set.prototype.createLifetimeKey = function(date, cb) {
  return client.zaddAsync([this.key, date, LIFETIME_KEY]);
};

/**
# @param float opts[time] : mean lifetime of an observation (secs).
# @param datetime opts[date] : a manual date to start decaying from.
*/
exports.create = function(options) {
  return new Promise(function createPromise(resolve, reject) {
    var o = options;

    if (typeof o !== typeof {}) return reject(new Error('Invalid options!'));

    if (!o.time) return reject(new Error('Invalid mean lifetime specified!'));

    if (!o.key) return reject(new Error('Invalid set key specified'));

    var date = o.date || new Date().getTime();
    var set = new Set(o.key);

    set.updateDecayDate(date)
      .then(function onUpdateDecayDate() {
        set.createLifetimeKey(o.time)
          .then(resolve)
          .catch(reject);
      })
      .catch(reject);
  });
};

exports.get = function(name) {
  return new Set(name);
};

exports.setRedisClient = function(_client) {
  client = _client;
};

exports.Set = Set;<|endoftext|>


<commit_before>var url      = require('url');
var util     = require('./util');
var crequest = require('./crequest');
var JStream  = require('jstream');

/**
 * Gets signature for formats.
 *
 * @param {String} link
 * @param {Object} info
 * @param {Function(Error, String)} callback
 */
exports.get = function(link, info, callback) {
  if (info.use_cipher_signature) {
    crequest(link, function(err, body) {
      if (err) return callback(err);

      var jsonStr = util.between(body, 'ytplayer.config = ', '</script>');
      if (!jsonStr) {
        return callback(new Error('could not find `ytplayer.config`'));
      }

      var jstream = new JStream();
      var ended = false;
      jstream.on('data', function(config) {
        ended = true;
        jstream.pause();

        var newFormats = util.parseFormats(config.args);
        var html5playerfile = 'http:' + config.assets.js;
        crequest(html5playerfile, function(err, body) {
          if (err) return callback(err);

          var tokens = exports.extractActions(body);
          if (!tokens) {
            callback(
              new Error('could not extract signature deciphering actions'));
            return;
          }

          info.formats = info.formats.map(function(format) {
            var newFormat = newFormats
              .filter(function(f) { return f.itag === format.itag; })[0];
            if (newFormat && newFormat.s) {
              format = newFormat;
            }
            var sig = exports.decipher(tokens, format.s);
            format.url = exports.getDownloadURL(format, sig);
            return format;
          });

          callback(null, info);
        });
      });
      jstream.on('error', function(err) {
        if (ended) { return; }
        callback(
          new Error('could not parse `ytplayer.config`: ' + err.message));
      });
      jstream.end(jsonStr);
    });

  } else {
    info.formats.forEach(function(format) {
      var sig = format.sig || '';
      format.url = exports.getDownloadURL(format, sig);
    });
    callback(null, info);
  }
};

/**
 * @param {Object} format
 * @param {Array.<String>} tokens
 */
exports.getDownloadURL = function(format, sig) {
  var decodedUrl;
  if (format.url) {
    decodedUrl = format.url;
  } else if (format.stream) {
    if (format.conn) {
      decodedUrl = format.conn;
      if (decodedUrl[decodedUrl.length - 1] !== '/') {
        decodedUrl += '/';
      }
      decodedUrl += format.stream;
    } else {
      decodedUrl = format.stream;
    }
  } else {
    console.warn('download url not found for itag ' + format.itag);
    return;
  }

  try {
    decodedUrl = decodeURIComponent(decodedUrl);
  } catch (err) {
    console.warn('could not decode url: ' + err.message);
    return;
  }

  // Make some adjustments to the final url.
  var parsedUrl = url.parse(decodedUrl, true);

  // Deleting the `search` part is necessary otherwise changes to
  // `query` won't reflect when running `url.format()`
  delete parsedUrl.search;

  var query = parsedUrl.query;
  query.ratebypass = 'yes';
  if (sig) {
    query.signature = sig;
  }

  return url.format(parsedUrl);
};

/**
 * Decipher a signature based on action tokens.
 *
 * @param {Array.<String>} tokens
 * @param {String} sig
 * @return {String}
 */
exports.decipher = function(tokens, sig) {
  sig = sig.split('');
  var pos;
  for (var i = 0, len = tokens.length; i < len; i++) {
    var token = tokens[i];
    switch (token[0]) {
    case 'r':
      sig = sig.reverse();
      break;
    case 'w':
      pos = ~~token.slice(1);
      sig = swapHeadAndPosition(sig, pos);
      break;
    case 's':
      pos = ~~token.slice(1);
      sig = sig.slice(pos);
    }
  }
  return sig.join('');
};

/**
 * Swaps the first element of an array with one of given position.
 *
 * @param {Array.<Object>} arr
 * @param {Number} position
 * @return {Array.<Object>}
 */
function swapHeadAndPosition(arr, position) {
  var first = arr[0];
  arr[0] = arr[position % arr.length];
  arr[position] = first;
  return arr;
}

/**
 * Extracts the actions that should be taken to decypher a signature.
 *
 * @param {String} body
 * @return {Array.<String>}
 */
var actionsRegexp = new RegExp(
  '([a-z])=\\1\\.split\\(""\\);' +
  '(?:\\1=([a-z]+)\\.(XG|NZ|PL)\\(\\1,\\d+\\);)+' +
  '(?:return |\\1=)\\1\\.join\\(""\\)'
);
exports.extractActions = function(body) {
  var result = actionsRegexp.exec(body);
  if (!result) { return null; }

  var funcbody = result[0];
  var sig = result[1];
  var obj = result[2];
  var myreg = sig + '=' + obj + '\\.(XG|NZ|PL)\\(' + sig + ',(\\d+)\\)';
  var tokenizeRegexp = new RegExp(myreg, 'g');
  var tokens = [];
  while ((result = tokenizeRegexp.exec(funcbody)) !== null) {
    switch (result[1]) {
      case 'XG':
        tokens.push('w' + result[2]);
        break;
      case 'NZ':
        tokens.push('r');
        break;
      case 'PL':
        tokens.push('s' + result[2]);
        break;
    }
  }
  return tokens;
};
<commit_msg>more sophisticated regexp to extract signature. fixes #4<commit_after>var url      = require('url');
var util     = require('./util');
var crequest = require('./crequest');
var JStream  = require('jstream');

/**
 * Gets signature for formats.
 *
 * @param {String} link
 * @param {Object} info
 * @param {Function(Error, String)} callback
 */
exports.get = function(link, info, callback) {
  if (info.use_cipher_signature) {
    crequest(link, function(err, body) {
      if (err) return callback(err);

      var jsonStr = util.between(body, 'ytplayer.config = ', '</script>');
      if (!jsonStr) {
        return callback(new Error('could not find `ytplayer.config`'));
      }

      var jstream = new JStream();
      var ended = false;
      jstream.on('data', function(config) {
        ended = true;
        jstream.pause();

        var newFormats = util.parseFormats(config.args);
        var html5playerfile = 'http:' + config.assets.js;
        crequest(html5playerfile, function(err, body) {
          if (err) return callback(err);

          var tokens = exports.extractActions(body);
          if (!tokens) {
            callback(
              new Error('could not extract signature deciphering actions'));
            return;
          }

          info.formats = info.formats.map(function(format) {
            var newFormat = newFormats
              .filter(function(f) { return f.itag === format.itag; })[0];
            if (newFormat && newFormat.s) {
              format = newFormat;
            }
            var sig = exports.decipher(tokens, format.s);
            format.url = exports.getDownloadURL(format, sig);
            return format;
          });

          callback(null, info);
        });
      });
      jstream.on('error', function(err) {
        if (ended) { return; }
        callback(
          new Error('could not parse `ytplayer.config`: ' + err.message));
      });
      jstream.end(jsonStr);
    });

  } else {
    info.formats.forEach(function(format) {
      var sig = format.sig || '';
      format.url = exports.getDownloadURL(format, sig);
    });
    callback(null, info);
  }
};

/**
 * @param {Object} format
 * @param {Array.<String>} tokens
 */
exports.getDownloadURL = function(format, sig) {
  var decodedUrl;
  if (format.url) {
    decodedUrl = format.url;
  } else if (format.stream) {
    if (format.conn) {
      decodedUrl = format.conn;
      if (decodedUrl[decodedUrl.length - 1] !== '/') {
        decodedUrl += '/';
      }
      decodedUrl += format.stream;
    } else {
      decodedUrl = format.stream;
    }
  } else {
    console.warn('download url not found for itag ' + format.itag);
    return;
  }

  try {
    decodedUrl = decodeURIComponent(decodedUrl);
  } catch (err) {
    console.warn('could not decode url: ' + err.message);
    return;
  }

  // Make some adjustments to the final url.
  var parsedUrl = url.parse(decodedUrl, true);

  // Deleting the `search` part is necessary otherwise changes to
  // `query` won't reflect when running `url.format()`
  delete parsedUrl.search;

  var query = parsedUrl.query;
  query.ratebypass = 'yes';
  if (sig) {
    query.signature = sig;
  }

  return url.format(parsedUrl);
};

/**
 * Decipher a signature based on action tokens.
 *
 * @param {Array.<String>} tokens
 * @param {String} sig
 * @return {String}
 */
exports.decipher = function(tokens, sig) {
  sig = sig.split('');
  var pos;
  for (var i = 0, len = tokens.length; i < len; i++) {
    var token = tokens[i];
    switch (token[0]) {
    case 'r':
      sig = sig.reverse();
      break;
    case 'w':
      pos = ~~token.slice(1);
      sig = swapHeadAndPosition(sig, pos);
      break;
    case 's':
      pos = ~~token.slice(1);
      sig = sig.slice(pos);
    }
  }
  return sig.join('');
};

/**
 * Swaps the first element of an array with one of given position.
 *
 * @param {Array.<Object>} arr
 * @param {Number} position
 * @return {Array.<Object>}
 */
function swapHeadAndPosition(arr, position) {
  var first = arr[0];
  arr[0] = arr[position % arr.length];
  arr[position] = first;
  return arr;
}

/* jshint maxlen:false */
/**
 * The original code in the html5player file will look like this:
 *
 * var lo={wS:function(a){return a.reverse()},IC:function(a,b){return a.slice(b)},rw:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b]=c;return a}};function mo(a){a=a.split("");a=lo.rw(a,1);a=lo.rw(a,32);a=lo.IC(a,1);a=lo.wS(a,77);a=lo.IC(a,3);a=lo.wS(a,77);a=lo.IC(a,3);a=lo.wS(a,44);return a.join("")}
 */
var actionsRegexp = new RegExp(
  'var (\\w+)=\\{((?:(?:' +
    '\\w+:function\\(a\\)\\{' +
      'return a\\.reverse\\(\\)' +
    '\\}|' +
    '\\w+:function\\(a,b\\)\\{' +
      'return a\\.slice\\(b\\)' +
    '\\}|' +
    '\\w+:function\\(a,b\\)\\{' +
      'var c=a\\[0\\];a\\[0\\]=a\\[b%a\\.length\\];a\\[b\\]=c;return a' +
    '\\}' +
  '),?){3})\\};' +
  'function \\w+\\(a\\)\\{' +
    'a=a\\.split\\(""\\);' +
    '((?:a=\\1\\.\\w+\\(a,\\d+\\);)+)' +
    'return a\\.join\\(""\\)' +
  '\\}'
);
var reverseRegexp = /(?:^|,)(\w+):function\(a\)\{return a\.reverse\(\)\}/;
var sliceRegexp = /(?:^|,)(\w+):function\(a,b\)\{return a\.slice\(b\)\}/;
var swapRegexp = new RegExp(
  '(?:^|,)(\\w+):function\\(a,b\\)\\{' +
    'var c=a\\[0\\];a\\[0\\]=a\\[b%a\\.length\\];a\\[b\\]=c;return a' +
  '\\}'
);

/**
 * Extracts the actions that should be taken to decypher a signature.
 *
 *
 * @param {String} body
 * @return {Array.<String>}
 */
exports.extractActions = function(body) {
  var result = actionsRegexp.exec(body);
  if (!result) { return null; }

  var obj      = result[1];
  var objBody  = result[2];
  var funcbody = result[3];

  result = reverseRegexp.exec(objBody);
  var reverseKey = result[1];
  result = sliceRegexp.exec(objBody);
  var sliceKey = result[1];
  result = swapRegexp.exec(objBody);
  var swapKey = result[1];

  var myreg = 'a=' + obj + '\\.(' +
    reverseKey + '|' + sliceKey + '|' + swapKey + ')\\(a,(\\d+)\\)';
  var tokenizeRegexp = new RegExp(myreg, 'g');
  var tokens = [];
  while ((result = tokenizeRegexp.exec(funcbody)) !== null) {
    switch (result[1]) {
      case swapKey:
        tokens.push('w' + result[2]);
        break;
      case reverseKey:
        tokens.push('r');
        break;
      case sliceKey:
        tokens.push('s' + result[2]);
        break;
    }
  }
  return tokens;
};
<|endoftext|>