<commit_before>exports.withinZone = function(name, zone) {
  var parts = name.split('.')
    , zparts
    , i, len;
  if (parts.length == 1) {
    // The name is not fully qualified, and therefore not in a zone.
    return zone ? false : true;
  }
  if (!zone) { return false; }
  if (zone == '.') { return true; }

  if (zone[zone.length - 1] == '.') { zone = zone.slice(0, -1); }
  zparts = zone.split('.');
  for (i = 0, len = zparts.length; i < len; ++i) {
    if (parts[parts.length - 1 - i] !== zparts[zparts.length - 1 - i]) { return false; }
  }
  return true;
};
<commit_msg>Less subtraction.<commit_after>exports.withinZone = function(name, zone) {
  var parts = name.split('.')
    , zparts
    , i, len;
  if (parts.length == 1) {
    // The name is not fully qualified, and therefore not in a zone.
    return zone ? false : true;
  }
  if (!zone) { return false; }
  if (zone == '.') { return true; }

  if (zone[zone.length - 1] == '.') { zone = zone.slice(0, -1); }
  zparts = zone.split('.');
  for (i = 1, len = zparts.length; i <= len; ++i) {
    if (parts[parts.length - i] !== zparts[zparts.length - i]) { return false; }
  }
  return true;
};
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var fs = require('fs')
  , path = require('path')
  , join = path.join
  , debug = require('debug')('watch');

/**
 * Ignored directories.
 */

var ignore = ['node_modules', '.git'];

/**
 * Escape special characters in the given string of html.
 *
 * @param  {String} html
 * @return {String}
 * @api private
 */

exports.escape = function(html) {
  return String(html)
    .replace(/&/g, '&')
    .replace(/"/g, '"')
    .replace(/</g, '<')
    .replace(/>/g, '>');
};

/**
 * Array#forEach (<=IE8)
 *
 * @param {Array} array
 * @param {Function} fn
 * @param {Object} scope
 * @api private
 */

exports.forEach = function(arr, fn, scope) {
  for (var i = 0, l = arr.length; i < l; i++)
    fn.call(scope, arr[i], i);
};

/**
 * Array#indexOf (<=IE8)
 *
 * @parma {Array} arr
 * @param {Object} obj to find index of
 * @param {Number} start
 * @api private
 */

exports.indexOf = function (arr, obj, start) {
  for (var i = start || 0, l = arr.length; i < l; i++) {
    if (arr[i] === obj)
      return i;
  }
  return -1;
};

/**
 * Array#reduce (<=IE8)
 *
 * @param {Array} array
 * @param {Function} fn
 * @param {Object} initial value
 * @param {Object} scope
 * @api private
 */

exports.reduce = function(arr, fn, val, scope) {
  var rval = val;

  for (var i = 0, l = arr.length; i < l; i++) {
    rval = fn.call(scope, rval, arr[i], i, arr);
  }

  return rval;
};

/**
 * Array#filter (<=IE8)
 *
 * @param {Array} array
 * @param {Function} fn
 * @param {Object} scope
 * @api private
 */

exports.filter = function(arr, fn, scope) {
  var ret = [];

  for (var i = 0, l = arr.length; i < l; i++) {
    var val = arr[i];
    if (fn.call(scope, val, i, arr))
      ret.push(val);
  }

  return ret;
};

/**
 * Object.keys (<=IE8)
 *
 * @param {Object} obj
 * @return {Array} keys
 * @api private
 */

exports.keys = Object.keys || function(obj) {
  var keys = []
    , has = Object.prototype.hasOwnProperty // for `window` on <=IE8

  for (var i in obj) {
    if (has.call(obj, i)) {
      keys.push(i);
    }
  }

  return keys;
};

/**
 * Watch the given `files` for changes
 * and invoke `fn(file)` on modification.
 *
 * @param {Array} files
 * @param {Function} fn
 * @api private
 */

exports.watch = function(files, fn){
  var options = { interval: 100 };
  files.forEach(function(file){
    debug('file %s', file);
    fs.watchFile(file, options, function(curr, prev){
      if (prev.mtime < curr.mtime) fn(file);
    });
  });
};

/**
 * Ignored files.
 */

function ignored(path){
  return !~ignore.indexOf(path);
}

/**
 * Lookup files in the given `dir`.
 *
 * @return {Array}
 * @api public
 */

exports.files = function(dir, ret){
  ret = ret || [];

  fs.readdirSync(dir)
  .filter(ignored)
  .forEach(function(path){
    path = join(dir, path);
    if (fs.statSync(path).isDirectory()) {
      exports.files(path, ret);
    } else if (path.match(/\.js$/)) {
      ret.push(path);
    }
  });

  return ret;
};<commit_msg>Added .coffee --watch support. Closes #242<commit_after>
/**
 * Module dependencies.
 */

var fs = require('fs')
  , path = require('path')
  , join = path.join
  , debug = require('debug')('watch');

/**
 * Ignored directories.
 */

var ignore = ['node_modules', '.git'];

/**
 * Escape special characters in the given string of html.
 *
 * @param  {String} html
 * @return {String}
 * @api private
 */

exports.escape = function(html) {
  return String(html)
    .replace(/&/g, '&')
    .replace(/"/g, '"')
    .replace(/</g, '<')
    .replace(/>/g, '>');
};

/**
 * Array#forEach (<=IE8)
 *
 * @param {Array} array
 * @param {Function} fn
 * @param {Object} scope
 * @api private
 */

exports.forEach = function(arr, fn, scope) {
  for (var i = 0, l = arr.length; i < l; i++)
    fn.call(scope, arr[i], i);
};

/**
 * Array#indexOf (<=IE8)
 *
 * @parma {Array} arr
 * @param {Object} obj to find index of
 * @param {Number} start
 * @api private
 */

exports.indexOf = function (arr, obj, start) {
  for (var i = start || 0, l = arr.length; i < l; i++) {
    if (arr[i] === obj)
      return i;
  }
  return -1;
};

/**
 * Array#reduce (<=IE8)
 *
 * @param {Array} array
 * @param {Function} fn
 * @param {Object} initial value
 * @param {Object} scope
 * @api private
 */

exports.reduce = function(arr, fn, val, scope) {
  var rval = val;

  for (var i = 0, l = arr.length; i < l; i++) {
    rval = fn.call(scope, rval, arr[i], i, arr);
  }

  return rval;
};

/**
 * Array#filter (<=IE8)
 *
 * @param {Array} array
 * @param {Function} fn
 * @param {Object} scope
 * @api private
 */

exports.filter = function(arr, fn, scope) {
  var ret = [];

  for (var i = 0, l = arr.length; i < l; i++) {
    var val = arr[i];
    if (fn.call(scope, val, i, arr))
      ret.push(val);
  }

  return ret;
};

/**
 * Object.keys (<=IE8)
 *
 * @param {Object} obj
 * @return {Array} keys
 * @api private
 */

exports.keys = Object.keys || function(obj) {
  var keys = []
    , has = Object.prototype.hasOwnProperty // for `window` on <=IE8

  for (var i in obj) {
    if (has.call(obj, i)) {
      keys.push(i);
    }
  }

  return keys;
};

/**
 * Watch the given `files` for changes
 * and invoke `fn(file)` on modification.
 *
 * @param {Array} files
 * @param {Function} fn
 * @api private
 */

exports.watch = function(files, fn){
  var options = { interval: 100 };
  files.forEach(function(file){
    debug('file %s', file);
    fs.watchFile(file, options, function(curr, prev){
      if (prev.mtime < curr.mtime) fn(file);
    });
  });
};

/**
 * Ignored files.
 */

function ignored(path){
  return !~ignore.indexOf(path);
}

/**
 * Lookup files in the given `dir`.
 *
 * @return {Array}
 * @api public
 */

exports.files = function(dir, ret){
  ret = ret || [];

  fs.readdirSync(dir)
  .filter(ignored)
  .forEach(function(path){
    path = join(dir, path);
    if (fs.statSync(path).isDirectory()) {
      exports.files(path, ret);
    } else if (path.match(/\.(js|coffee)$/)) {
      ret.push(path);
    }
  });

  return ret;
};<|endoftext|>


<commit_before>'use strict';

var Base = require('base-methods');
var decorate = require('./decorate');
var utils = require('./utils');

/**
 * Create an instance of `Views` with the given `options`.
 *
 * ```js
 * var collection = new Views();
 * collection.addView('foo', {content: 'bar'});
 * ```
 * @param {Object} `options`
 * @api public
 */

function Views(options) {
  Base.call(this);
  options = options || {};

  // decorate the instance
  utils.renameKey(this);
  decorate.init(this);

  // add constructors
  this.define('List', options.List || require('./list'));
  this.define('View', options.View || require('./view'));
  this.define('loaded', false);
  this.define('plugins', []);

  this.isCollection = true;
  this.queue = [];
  this.views = {};

  // if an instance of `List` of `Views` is passed, load it now
  if (Array.isArray(options) || options instanceof this.List) {
    this.options = options.options;
    this.addList(options.items);

  } else if (options instanceof Views) {
    this.options = options.options;
    this.addViews(options.views);

  } else {
    this.options = options;
  }
}

Base.extend(Views);

decorate.config(Views.prototype);
decorate.routes(Views.prototype);
decorate.engine(Views.prototype);
decorate.context(Views.prototype);
decorate.helpers(Views.prototype);
decorate.layout(Views.prototype);
decorate.render(Views.prototype);
decorate.lookup(Views.prototype);
decorate.errors(Views.prototype, 'Views');

/**
 * Run a plugin on the collection instance. Plugins
 * are invoked immediately upon creating the collection
 * in the order in which they were defined.
 *
 * ```js
 * collection.use(function(views) {
 *   // `views` is the instance, as is `this`
 *
 *   // optionally return a function to be passed to
 *   // the `.use` method of each view created on the
 *   // instance
 *   return function(view) {
 *     // do stuff to each `view`
 *   };
 * });
 * ```
 *
 * @param {Function} `fn` Plugin function. If the plugin returns a function it will be passed to the `use` method of each view created on the instance.
 * @return {Object} Returns the instance for chaining.
 * @api public
 */

Views.prototype.use = function(fn) {
  var plugin = fn.call(this, this, this.options);
  if (typeof plugin === 'function') {
    this.plugins.push(plugin);
  }
  this.emit('use');
  return this;
};

/**
 * Returns a new view, using the `View` class
 * currently defined on the instance.
 *
 * ```js
 * var view = app.view('foo', {conetent: '...'});
 * // or
 * var view = app.view({path: 'foo', conetent: '...'});
 * ```
 * @name .view
 * @param {String|Object} `key` View key or object
 * @param {Object} `value` If key is a string, value is the view object.
 * @return {Object} returns the `view` object
 * @api public
 */

utils.itemFactory(Views.prototype, 'view', 'View');

/**
 * Set a view on the collection. This is identical to [addView](#addView)
 * except `setView` does not emit an event for each view.
 *
 * ```js
 * collection.setView('foo', {content: 'bar'});
 * ```
 *
 * @param {String|Object} `key` View key or object
 * @param {Object} `value` If key is a string, value is the view object.
 * @developer This method is decorated onto the collection in the constructor using the `createView` utility method.
 * @return {Object} returns the `view` instance.
 * @api public
 */

Views.prototype.setView = function(key, value) {
  var view = this.view(key, value);
  this.views[view.key] = view;
  return view;
};

/**
 * Adds event emitting and custom loading to [setView](#setView).
 *
 * @param {String} `key`
 * @param {Object} `value`
 * @api public
 */

Views.prototype.addView = function(key, value) {
  var args = [].slice.call(arguments);
  this.emit.apply(this, ['addView'].concat(args));

  var view = this.setView(key, value);
  while (this.queue.length) {
    this.setView(this.queue.shift());
  }

  this.extendView(view);
  return view;
};

/**
 * Load multiple views onto the collection.
 *
 * ```js
 * collection.addViews({
 *   'a.html': {content: '...'},
 *   'b.html': {content: '...'},
 *   'c.html': {content: '...'}
 * });
 * ```
 * @param {Object|Array} `views`
 * @return {Object} returns the `collection` object
 * @api public
 */

Views.prototype.addViews = function(views) {
  if (Array.isArray(views)) {
    return this.addList.apply(this, arguments);
  }
  if (arguments.length > 1 && utils.isView(arguments[1])) {
    return this.addView.apply(this, arguments);
  }

  this.emit('addViews', views);
  if (this.loaded) return this;

  this.visit('addView', views);
  return this;
};

/**
 * Load an array of views onto the collection.
 *
 * ```js
 * collection.addList([
 *   {path: 'a.html', content: '...'},
 *   {path: 'b.html', content: '...'},
 *   {path: 'c.html', content: '...'}
 * ]);
 * ```
 * @param {Array} `list`
 * @return {Object} returns the `views` instance
 * @api public
 */

Views.prototype.addList = function(list) {
  this.emit('addList', list);
  if (this.loaded) return this;

  if (!Array.isArray(list)) {
    throw new TypeError('expected list to be an array.');
  }

  var len = list.length, i = -1;
  while (++i < len) {
    var view = list[i];
    this.addView(view.path, view);
  }
  return this;
};

/**
 * Get a view from the collection.
 *
 * ```js
 * collection.getView('a.html');
 * ```
 * @param {String} `key` Key of the view to get.
 * @return {Object}
 * @api public
 */

Views.prototype.getView = function(key) {
  return this.views[key] || this.views[this.renameKey(key)];
};

Views.prototype.extendView = function(view) {
  decorate.view.all(this, view);
};

/**
 * Set view types for the collection.
 *
 * @param {String} `plural` e.g. `pages`
 * @param {Object} `options`
 * @api private
 */

Views.prototype.viewType = function() {
  this.options.viewType = utils.arrayify(this.options.viewType || []);
  if (this.options.viewType.length === 0) {
    this.options.viewType.push('renderable');
  }
  return this.options.viewType;
};

/**
 * Expose `Views`
 */

module.exports = Views;
<commit_msg>adds `loadView` method<commit_after>'use strict';

var fs = require('fs');
var path = require('path');
var Base = require('base-methods');
var decorate = require('./decorate');
var utils = require('./utils');

/**
 * Create an instance of `Views` with the given `options`.
 *
 * ```js
 * var collection = new Views();
 * collection.addView('foo', {content: 'bar'});
 * ```
 * @param {Object} `options`
 * @api public
 */

function Views(options) {
  Base.call(this);
  options = options || {};

  // decorate the instance
  utils.renameKey(this);
  decorate.init(this);

  // add constructors
  this.define('List', options.List || require('./list'));
  this.define('View', options.View || require('./view'));
  this.define('loaded', false);
  this.define('plugins', []);

  this.isCollection = true;
  this.queue = [];
  this.views = {};

  // if an instance of `List` of `Views` is passed, load it now
  if (Array.isArray(options) || options instanceof this.List) {
    this.options = options.options;
    this.addList(options.items);

  } else if (options instanceof Views) {
    this.options = options.options;
    this.addViews(options.views);

  } else {
    this.options = options;
  }
}

Base.extend(Views);

decorate.config(Views.prototype);
decorate.routes(Views.prototype);
decorate.engine(Views.prototype);
decorate.context(Views.prototype);
decorate.helpers(Views.prototype);
decorate.layout(Views.prototype);
decorate.render(Views.prototype);
decorate.lookup(Views.prototype);
decorate.errors(Views.prototype, 'Views');

/**
 * Run a plugin on the collection instance. Plugins
 * are invoked immediately upon creating the collection
 * in the order in which they were defined.
 *
 * ```js
 * collection.use(function(views) {
 *   // `views` is the instance, as is `this`
 *
 *   // optionally return a function to be passed to
 *   // the `.use` method of each view created on the
 *   // instance
 *   return function(view) {
 *     // do stuff to each `view`
 *   };
 * });
 * ```
 *
 * @param {Function} `fn` Plugin function. If the plugin returns a function it will be passed to the `use` method of each view created on the instance.
 * @return {Object} Returns the instance for chaining.
 * @api public
 */

Views.prototype.use = function(fn) {
  var plugin = fn.call(this, this, this.options);
  if (typeof plugin === 'function') {
    this.plugins.push(plugin);
  }
  this.emit('use');
  return this;
};

/**
 * Returns a new view, using the `View` class
 * currently defined on the instance.
 *
 * ```js
 * var view = app.view('foo', {conetent: '...'});
 * // or
 * var view = app.view({path: 'foo', conetent: '...'});
 * ```
 * @name .view
 * @param {String|Object} `key` View key or object
 * @param {Object} `value` If key is a string, value is the view object.
 * @return {Object} returns the `view` object
 * @api public
 */

utils.itemFactory(Views.prototype, 'view', 'View');

/**
 * Set a view on the collection. This is identical to [addView](#addView)
 * except `setView` does not emit an event for each view.
 *
 * ```js
 * collection.setView('foo', {content: 'bar'});
 * ```
 *
 * @param {String|Object} `key` View key or object
 * @param {Object} `value` If key is a string, value is the view object.
 * @developer This method is decorated onto the collection in the constructor using the `createView` utility method.
 * @return {Object} returns the `view` instance.
 * @api public
 */

Views.prototype.setView = function(key, value) {
  var view = this.view(key, value);
  this.views[view.key] = view;
  return view;
};

/**
 * Adds event emitting and custom loading to [setView](#setView).
 *
 * @param {String} `key`
 * @param {Object} `value`
 * @api public
 */

Views.prototype.addView = function(key, value) {
  var args = [].slice.call(arguments);
  this.emit.apply(this, ['addView'].concat(args));

  var view = this.setView(key, value);
  while (this.queue.length) {
    this.setView(this.queue.shift());
  }

  this.extendView(view);
  return view;
};

/**
 * Load multiple views onto the collection.
 *
 * ```js
 * collection.addViews({
 *   'a.html': {content: '...'},
 *   'b.html': {content: '...'},
 *   'c.html': {content: '...'}
 * });
 * ```
 * @param {Object|Array} `views`
 * @return {Object} returns the `collection` object
 * @api public
 */

Views.prototype.addViews = function(views) {
  if (Array.isArray(views)) {
    return this.addList.apply(this, arguments);
  }
  if (arguments.length > 1 && utils.isView(arguments[1])) {
    return this.addView.apply(this, arguments);
  }

  this.emit('addViews', views);
  if (this.loaded) return this;

  this.visit('addView', views);
  return this;
};

/**
 * Load an array of views onto the collection.
 *
 * ```js
 * collection.addList([
 *   {path: 'a.html', content: '...'},
 *   {path: 'b.html', content: '...'},
 *   {path: 'c.html', content: '...'}
 * ]);
 * ```
 * @param {Array} `list`
 * @return {Object} returns the `views` instance
 * @api public
 */

Views.prototype.addList = function(list) {
  this.emit('addList', list);
  if (this.loaded) return this;

  if (!Array.isArray(list)) {
    throw new TypeError('expected list to be an array.');
  }

  var len = list.length, i = -1;
  while (++i < len) {
    var view = list[i];
    this.addView(view.path, view);
  }
  return this;
};

/**
 * Loads and create a new `View` from the file system.
 *
 * @param {String} `filename`
 * @param {Object} `options`
 * @return {Object} Returns view object
 * @api public
 */

Views.prototype.loadView = function(filename, options) {
  var opts = utils.merge({}, this.options, options);
  var View = this.get('View');

  var extname = path.extname(filename);
  var name = path.basename(filename, extname);
  var fp = path.resolve(opts.cwd, name);
  var ext = opts.ext || extname || '';
  var str = utils.tryRead(fp + ext) || utils.tryRead(fp);

  var view = new View({
    path: fp + ext,
    name: name,
    ext: ext,
    content: str
  });

  return this.addView(view);
};

/**
 * Get a view from the collection.
 *
 * ```js
 * collection.getView('a.html');
 * ```
 * @param {String} `key` Key of the view to get.
 * @return {Object}
 * @api public
 */

Views.prototype.getView = function(key) {
  return this.views[key] || this.views[this.renameKey(key)];
};

/**
 * Decorate each view on the collection with additional methods
 * and properties. This provides a way of easily overriding
 * defaults.
 *
 * @param {Object} `view`
 * @return {Object}
 * @api public
 */

Views.prototype.extendView = function(view) {
  decorate.view.all(this, view);
};

/**
 * Set view types for the collection.
 *
 * @param {String} `plural` e.g. `pages`
 * @param {Object} `options`
 * @api private
 */

Views.prototype.viewType = function() {
  this.options.viewType = utils.arrayify(this.options.viewType || []);
  if (this.options.viewType.length === 0) {
    this.options.viewType.push('renderable');
  }
  return this.options.viewType;
};

/**
 * Expose `Views`
 */

module.exports = Views;
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2014, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * https://raw.github.com/facebook/regenerator/master/LICENSE file. An
 * additional grant of patent rights can be found in the PATENTS file in
 * the same directory.
 */

var assert = require("assert");
var fs = require("fs");
var recast = require("recast");
var types = recast.types;
var n = types.namedTypes;
var b = types.builders;
var isArray = types.builtInTypes.array;
var isObject = types.builtInTypes.object;
var NodePath = types.NodePath;
var hoist = require("./hoist").hoist;
var Emitter = require("./emit").Emitter;
var runtimeProperty = require("./util").runtimeProperty;
var runtimeWrapMethod = runtimeProperty("wrap");
var runtimeMarkMethod = runtimeProperty("mark");
var runtimeValuesMethod = runtimeProperty("values");
var runtimeAsyncMethod = runtimeProperty("async");

exports.transform = function transform(node, options) {
  node = recast.visit(node, visitor);

  if (options && options.includeRuntime) {
    injectRuntime(n.File.check(node) ? node.program : node);
  }

  return node;
};

function injectRuntime(program) {
  n.Program.assert(program);

  // Include the runtime by modifying the AST rather than by concatenating
  // strings. This technique will allow for more accurate source mapping.
  var runtimePath = require("..").runtime.path;
  var runtime = fs.readFileSync(runtimePath, "utf8");
  var runtimeBody = recast.parse(runtime, {
    sourceFileName: runtimePath
  }).program.body;

  var body = program.body;
  body.unshift.apply(body, runtimeBody);
}

var visitor = types.PathVisitor.fromMethodsObject({
  visitFunction: function(path) {
    // Calling this.traverse(path) first makes for a post-order traversal.
    this.traverse(path);

    var node = path.value;

    if (!node.generator && !node.async) {
      return;
    }

    node.generator = false;

    if (node.expression) {
      // Transform expression lambdas into normal functions.
      node.expression = false;
      node.body = b.blockStatement([
        b.returnStatement(node.body)
      ]);
    }

    if (node.async) {
      awaitVisitor.visit(path.get("body"));
    }

    var outerFnId = node.id || (
      node.id = path.scope.parent.declareTemporary("callee$")
    );

    var innerFnId = b.identifier(node.id.name + "$");
    var contextId = path.scope.declareTemporary("context$");
    var argsId = path.scope.declareTemporary("args$");
    var shouldAliasArguments = renameArguments(path, argsId);
    var vars = hoist(path);

    if (shouldAliasArguments) {
      vars = vars || b.variableDeclaration("var", []);
      vars.declarations.push(b.variableDeclarator(
        argsId, b.identifier("arguments")
      ));
    }

    var emitter = new Emitter(contextId);
    emitter.explode(path.get("body"));

    var outerBody = [];

    if (vars && vars.declarations.length > 0) {
      outerBody.push(vars);
    }

    var wrapArgs = [
      emitter.getContextFunction(innerFnId),
      // Async functions don't care about the outer function because they
      // don't need it to be marked and don't inherit from its .prototype.
      node.async ? b.literal(null) : outerFnId,
      b.thisExpression()
    ];

    var tryEntryList = emitter.getTryEntryList();
    if (tryEntryList) {
      wrapArgs.push(tryEntryList);
    }

    var wrapCall = b.callExpression(
      node.async ? runtimeAsyncMethod : runtimeWrapMethod,
      wrapArgs
    );

    outerBody.push(b.returnStatement(wrapCall));
    node.body = b.blockStatement(outerBody);

    if (node.async) {
      node.async = false;
      return;
    }

    if (n.FunctionDeclaration.check(node)) {
      var pp = path.parent;

      while (pp && !(n.BlockStatement.check(pp.value) ||
                     n.Program.check(pp.value))) {
        pp = pp.parent;
      }

      if (!pp) {
        return;
      }

      // Here we turn the FunctionDeclaration into a named
      // FunctionExpression that will be assigned to a variable of the
      // same name at the top of the enclosing block. This is important
      // for a very subtle reason: named function expressions can refer to
      // themselves by name without fear that the binding may change due
      // to code executing outside the function, whereas function
      // declarations are vulnerable to the following rebinding:
      //
      //   function f() { return f }
      //   var g = f;
      //   f = "asdf";
      //   g(); // "asdf"
      //
      // One way to prevent the problem illustrated above is to transform
      // the function declaration thus:
      //
      //   var f = function f() { return f };
      //   var g = f;
      //   f = "asdf";
      //   g(); // f
      //   g()()()()(); // f
      //
      // In the code below, we transform generator function declarations
      // in the following way:
      //
      //   gen().next(); // { value: gen, done: true }
      //   function *gen() {
      //     return gen;
      //   }
      //
      // becomes something like
      //
      //   var gen = runtime.mark(function *gen() {
      //     return gen;
      //   });
      //   gen().next(); // { value: gen, done: true }
      //
      // which ensures that the generator body can always reliably refer
      // to gen by name.

      // Remove the FunctionDeclaration so that we can add it back as a
      // FunctionExpression passed to runtime.mark.
      path.replace();

      // Change the type of the function to be an expression instead of a
      // declaration. Note that all the other fields are the same.
      node.type = "FunctionExpression";

      var varDecl = b.variableDeclaration("var", [
        b.variableDeclarator(
          node.id,
          b.callExpression(runtimeMarkMethod, [node])
        )
      ]);

      if (node.comments) {
        // Copy any comments preceding the function declaration to the
        // variable declaration, to avoid weird formatting consequences.
        varDecl.comments = node.comments;
        node.comments = null;
      }

      var bodyPath = pp.get("body");
      var bodyLen = bodyPath.value.length;

      for (var i = 0; i < bodyLen; ++i) {
        var firstStmtPath = bodyPath.get(i);
        if (!shouldNotHoistAbove(firstStmtPath)) {
          firstStmtPath.insertBefore(varDecl);
          return;
        }
      }

      bodyPath.push(varDecl);

    } else {
      n.FunctionExpression.assert(node);
      return b.callExpression(runtimeMarkMethod, [node]);
    }
  },

  visitForOfStatement: function(path) {
    this.traverse(path);

    var node = path.value;
    var tempIterId = path.scope.declareTemporary("t$");
    var tempIterDecl = b.variableDeclarator(
      tempIterId,
      b.callExpression(
        runtimeValuesMethod,
        [node.right]
      )
    );

    var tempInfoId = path.scope.declareTemporary("t$");
    var tempInfoDecl = b.variableDeclarator(tempInfoId, null);

    var init = node.left;
    var loopId;
    if (n.VariableDeclaration.check(init)) {
      loopId = init.declarations[0].id;
      init.declarations.push(tempIterDecl, tempInfoDecl);
    } else {
      loopId = init;
      init = b.variableDeclaration("var", [
        tempIterDecl,
        tempInfoDecl
      ]);
    }
    n.Identifier.assert(loopId);

    var loopIdAssignExprStmt = b.expressionStatement(
      b.assignmentExpression(
        "=",
        loopId,
        b.memberExpression(
          tempInfoId,
          b.identifier("value"),
          false
        )
      )
    );

    if (n.BlockStatement.check(node.body)) {
      node.body.body.unshift(loopIdAssignExprStmt);
    } else {
      node.body = b.blockStatement([
        loopIdAssignExprStmt,
        node.body
      ]);
    }

    return b.forStatement(
      init,
      b.unaryExpression(
        "!",
        b.memberExpression(
          b.assignmentExpression(
            "=",
            tempInfoId,
            b.callExpression(
              b.memberExpression(
                tempIterId,
                b.identifier("next"),
                false
              ),
              []
            )
          ),
          b.identifier("done"),
          false
        )
      ),
      null,
      node.body
    );
  }
});

function shouldNotHoistAbove(stmtPath) {
  var value = stmtPath.value;
  n.Statement.assert(value);

  // If the first statement is a "use strict" declaration, make sure to
  // insert hoisted declarations afterwards.
  if (n.ExpressionStatement.check(value) &&
      n.Literal.check(value.expression) &&
      value.expression.value === "use strict") {
    return true;
  }

  if (n.VariableDeclaration.check(value)) {
    for (var i = 0; i < value.declarations.length; ++i) {
      var decl = value.declarations[i];
      if (n.CallExpression.check(decl.init) &&
          types.astNodesAreEquivalent(decl.init.callee,
                                      runtimeMarkMethod)) {
        return true;
      }
    }
  }

  return false;
}

function renameArguments(funcPath, argsId) {
  assert.ok(funcPath instanceof types.NodePath);
  var func = funcPath.value;
  var didReplaceArguments = false;
  var hasImplicitArguments = false;

  recast.visit(funcPath, {
    visitFunction: function(path) {
      if (path.value === func) {
        hasImplicitArguments = !path.scope.lookup("arguments");
        this.traverse(path);
      } else {
        return false;
      }
    },

    visitIdentifier: function(path) {
      if (path.value.name === "arguments") {
        var isMemberProperty =
          n.MemberExpression.check(path.parent.node) &&
          path.name === "property" &&
          !path.parent.node.computed;

        if (!isMemberProperty) {
          path.replace(argsId);
          didReplaceArguments = true;
          return false;
        }
      }

      this.traverse(path);
    }
  });

  // If the traversal replaced any arguments identifiers, and those
  // identifiers were free variables, then we need to alias the outer
  // function's arguments object to the variable named by argsId.
  return didReplaceArguments && hasImplicitArguments;
}

var awaitVisitor = types.PathVisitor.fromMethodsObject({
  visitFunction: function(path) {
    return false; // Don't descend into nested function scopes.
  },

  visitAwaitExpression: function(path) {
    // Convert await expressions to yield expressions.
    return b.yieldExpression(path.value.argument, false);
  }
});
<commit_msg>Report back if any changes was made to AST. Fixes #152 This is done by using the proposed ast-types API from: benjamn/ast-types#79<commit_after>/**
 * Copyright (c) 2014, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * https://raw.github.com/facebook/regenerator/master/LICENSE file. An
 * additional grant of patent rights can be found in the PATENTS file in
 * the same directory.
 */

var assert = require("assert");
var fs = require("fs");
var recast = require("recast");
var types = recast.types;
var n = types.namedTypes;
var b = types.builders;
var isArray = types.builtInTypes.array;
var isObject = types.builtInTypes.object;
var NodePath = types.NodePath;
var hoist = require("./hoist").hoist;
var Emitter = require("./emit").Emitter;
var runtimeProperty = require("./util").runtimeProperty;
var runtimeWrapMethod = runtimeProperty("wrap");
var runtimeMarkMethod = runtimeProperty("mark");
var runtimeValuesMethod = runtimeProperty("values");
var runtimeAsyncMethod = runtimeProperty("async");

exports.transform = function transform(node, options) {
  var visitor = types.PathVisitor.fromMethodsObject(visitorMethods);

  node = recast.visit(node, visitor);

  if (options && options.includeRuntime && visitor.wasChangeReported()) {
    injectRuntime(n.File.check(node) ? node.program : node);
  }

  options.madeChanges = visitor.wasChangeReported();

  return node;
};

function injectRuntime(program) {
  n.Program.assert(program);

  // Include the runtime by modifying the AST rather than by concatenating
  // strings. This technique will allow for more accurate source mapping.
  var runtimePath = require("..").runtime.path;
  var runtime = fs.readFileSync(runtimePath, "utf8");
  var runtimeBody = recast.parse(runtime, {
    sourceFileName: runtimePath
  }).program.body;

  var body = program.body;
  body.unshift.apply(body, runtimeBody);
}

var visitorMethods = {
  visitFunction: function(path) {
    // Calling this.traverse(path) first makes for a post-order traversal.
    this.traverse(path);

    var node = path.value;

    if (!node.generator && !node.async) {
      return;
    }

    this.reportChanged();

    node.generator = false;

    if (node.expression) {
      // Transform expression lambdas into normal functions.
      node.expression = false;
      node.body = b.blockStatement([
        b.returnStatement(node.body)
      ]);
    }

    if (node.async) {
      awaitVisitor.visit(path.get("body"));
    }

    var outerFnId = node.id || (
      node.id = path.scope.parent.declareTemporary("callee$")
    );

    var innerFnId = b.identifier(node.id.name + "$");
    var contextId = path.scope.declareTemporary("context$");
    var argsId = path.scope.declareTemporary("args$");
    var shouldAliasArguments = renameArguments(path, argsId);
    var vars = hoist(path);

    if (shouldAliasArguments) {
      vars = vars || b.variableDeclaration("var", []);
      vars.declarations.push(b.variableDeclarator(
        argsId, b.identifier("arguments")
      ));
    }

    var emitter = new Emitter(contextId);
    emitter.explode(path.get("body"));

    var outerBody = [];

    if (vars && vars.declarations.length > 0) {
      outerBody.push(vars);
    }

    var wrapArgs = [
      emitter.getContextFunction(innerFnId),
      // Async functions don't care about the outer function because they
      // don't need it to be marked and don't inherit from its .prototype.
      node.async ? b.literal(null) : outerFnId,
      b.thisExpression()
    ];

    var tryEntryList = emitter.getTryEntryList();
    if (tryEntryList) {
      wrapArgs.push(tryEntryList);
    }

    var wrapCall = b.callExpression(
      node.async ? runtimeAsyncMethod : runtimeWrapMethod,
      wrapArgs
    );

    outerBody.push(b.returnStatement(wrapCall));
    node.body = b.blockStatement(outerBody);

    if (node.async) {
      node.async = false;
      return;
    }

    if (n.FunctionDeclaration.check(node)) {
      var pp = path.parent;

      while (pp && !(n.BlockStatement.check(pp.value) ||
                     n.Program.check(pp.value))) {
        pp = pp.parent;
      }

      if (!pp) {
        return;
      }

      // Here we turn the FunctionDeclaration into a named
      // FunctionExpression that will be assigned to a variable of the
      // same name at the top of the enclosing block. This is important
      // for a very subtle reason: named function expressions can refer to
      // themselves by name without fear that the binding may change due
      // to code executing outside the function, whereas function
      // declarations are vulnerable to the following rebinding:
      //
      //   function f() { return f }
      //   var g = f;
      //   f = "asdf";
      //   g(); // "asdf"
      //
      // One way to prevent the problem illustrated above is to transform
      // the function declaration thus:
      //
      //   var f = function f() { return f };
      //   var g = f;
      //   f = "asdf";
      //   g(); // f
      //   g()()()()(); // f
      //
      // In the code below, we transform generator function declarations
      // in the following way:
      //
      //   gen().next(); // { value: gen, done: true }
      //   function *gen() {
      //     return gen;
      //   }
      //
      // becomes something like
      //
      //   var gen = runtime.mark(function *gen() {
      //     return gen;
      //   });
      //   gen().next(); // { value: gen, done: true }
      //
      // which ensures that the generator body can always reliably refer
      // to gen by name.

      // Remove the FunctionDeclaration so that we can add it back as a
      // FunctionExpression passed to runtime.mark.
      path.replace();

      // Change the type of the function to be an expression instead of a
      // declaration. Note that all the other fields are the same.
      node.type = "FunctionExpression";

      var varDecl = b.variableDeclaration("var", [
        b.variableDeclarator(
          node.id,
          b.callExpression(runtimeMarkMethod, [node])
        )
      ]);

      if (node.comments) {
        // Copy any comments preceding the function declaration to the
        // variable declaration, to avoid weird formatting consequences.
        varDecl.comments = node.comments;
        node.comments = null;
      }

      var bodyPath = pp.get("body");
      var bodyLen = bodyPath.value.length;

      for (var i = 0; i < bodyLen; ++i) {
        var firstStmtPath = bodyPath.get(i);
        if (!shouldNotHoistAbove(firstStmtPath)) {
          firstStmtPath.insertBefore(varDecl);
          return;
        }
      }

      bodyPath.push(varDecl);

    } else {
      n.FunctionExpression.assert(node);
      return b.callExpression(runtimeMarkMethod, [node]);
    }
  },

  visitForOfStatement: function(path) {
    this.traverse(path);

    var node = path.value;
    var tempIterId = path.scope.declareTemporary("t$");
    var tempIterDecl = b.variableDeclarator(
      tempIterId,
      b.callExpression(
        runtimeValuesMethod,
        [node.right]
      )
    );

    var tempInfoId = path.scope.declareTemporary("t$");
    var tempInfoDecl = b.variableDeclarator(tempInfoId, null);

    var init = node.left;
    var loopId;
    if (n.VariableDeclaration.check(init)) {
      loopId = init.declarations[0].id;
      init.declarations.push(tempIterDecl, tempInfoDecl);
    } else {
      loopId = init;
      init = b.variableDeclaration("var", [
        tempIterDecl,
        tempInfoDecl
      ]);
    }
    n.Identifier.assert(loopId);

    var loopIdAssignExprStmt = b.expressionStatement(
      b.assignmentExpression(
        "=",
        loopId,
        b.memberExpression(
          tempInfoId,
          b.identifier("value"),
          false
        )
      )
    );

    if (n.BlockStatement.check(node.body)) {
      node.body.body.unshift(loopIdAssignExprStmt);
    } else {
      node.body = b.blockStatement([
        loopIdAssignExprStmt,
        node.body
      ]);
    }

    return b.forStatement(
      init,
      b.unaryExpression(
        "!",
        b.memberExpression(
          b.assignmentExpression(
            "=",
            tempInfoId,
            b.callExpression(
              b.memberExpression(
                tempIterId,
                b.identifier("next"),
                false
              ),
              []
            )
          ),
          b.identifier("done"),
          false
        )
      ),
      null,
      node.body
    );
  }
};

function shouldNotHoistAbove(stmtPath) {
  var value = stmtPath.value;
  n.Statement.assert(value);

  // If the first statement is a "use strict" declaration, make sure to
  // insert hoisted declarations afterwards.
  if (n.ExpressionStatement.check(value) &&
      n.Literal.check(value.expression) &&
      value.expression.value === "use strict") {
    return true;
  }

  if (n.VariableDeclaration.check(value)) {
    for (var i = 0; i < value.declarations.length; ++i) {
      var decl = value.declarations[i];
      if (n.CallExpression.check(decl.init) &&
          types.astNodesAreEquivalent(decl.init.callee,
                                      runtimeMarkMethod)) {
        return true;
      }
    }
  }

  return false;
}

function renameArguments(funcPath, argsId) {
  assert.ok(funcPath instanceof types.NodePath);
  var func = funcPath.value;
  var didReplaceArguments = false;
  var hasImplicitArguments = false;

  recast.visit(funcPath, {
    visitFunction: function(path) {
      if (path.value === func) {
        hasImplicitArguments = !path.scope.lookup("arguments");
        this.traverse(path);
      } else {
        return false;
      }
    },

    visitIdentifier: function(path) {
      if (path.value.name === "arguments") {
        var isMemberProperty =
          n.MemberExpression.check(path.parent.node) &&
          path.name === "property" &&
          !path.parent.node.computed;

        if (!isMemberProperty) {
          path.replace(argsId);
          didReplaceArguments = true;
          return false;
        }
      }

      this.traverse(path);
    }
  });

  // If the traversal replaced any arguments identifiers, and those
  // identifiers were free variables, then we need to alias the outer
  // function's arguments object to the variable named by argsId.
  return didReplaceArguments && hasImplicitArguments;
}

var awaitVisitor = types.PathVisitor.fromMethodsObject({
  visitFunction: function(path) {
    return false; // Don't descend into nested function scopes.
  },

  visitAwaitExpression: function(path) {
    // Convert await expressions to yield expressions.
    return b.yieldExpression(path.value.argument, false);
  }
});
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var util = require('util');
var events = require('events');

var hasNativeRecursive = require('./has-native-recursive');
var is = require('./is');

var EVENT_UPDATE = 'update';
var EVENT_REMOVE = 'remove';

var SKIP_FLAG = Symbol('skip');

function hasDup(arr) {
  return arr.some(function(v, i, self) {
    return self.indexOf(v) !== i;
  });
}

function unique(arr) {
  return arr.filter(function(v, i, self) {
    return self.indexOf(v) === i;
  });
}

// One level flat
function flat1(arr) {
  return arr.reduce(function(acc, v) {
    return acc.concat(v);
  }, []);
}

function assertEncoding(encoding) {
  if (encoding && encoding !== 'buffer' && !Buffer.isEncoding(encoding)) {
    throw new Error('Unknown encoding: ' + encoding);
  }
}

function guard(fn) {
  if (is.func(fn))
    return function(arg, action) {
      if (fn(arg, false)) action();
    }
  if (is.regExp(fn))
    return function(arg, action) {
      if (fn.test(arg)) action();
    }
  return function(arg, action) {
      action();
  }
}

function composeMessage(names) {
  return names.map(function(n) {
    return is.exists(n)
      ? [EVENT_UPDATE, n]
      : [EVENT_REMOVE, n];
  });
}

function getMessages(cache) {
  var filtered = unique(cache);

  // Saving file from an editor? If so, assuming the
  // non-existed files in the cache are temporary files
  // generated by an editor and thus be filtered.
  var reg = /~$|^\.#|^##$/g;
  var hasSpecialChar = cache.some(function(c) {
    return reg.test(c);
  });

  if (hasSpecialChar) {
    var dup = hasDup(cache.map(function(c) {
      return c.replace(reg, '');
    }));
    if (dup) {
      filtered = filtered.filter(function(m) {
        return is.exists(m);
      });
    }
  }

  return composeMessage(filtered);
}

function debounce(info, fn) {
  var timer, cache = [];
  var encoding = info.options.encoding;
  var delay = info.options.delay;
  if (!is.number(delay)) {
    delay = 200;
  }
  function handle() {
    getMessages(cache).forEach(function(msg) {
      msg[1] = Buffer.from(msg[1]);
      if (encoding !== 'buffer') {
        msg[1] = msg[1].toString(encoding);
      }
      fn.apply(null, msg);
    });
    timer = null;
    cache = [];
  }
  return function(rawEvt, name) {
    cache.push(name);
    if (!timer) {
      timer = setTimeout(handle, delay);
    }
  }
}

function createDupsFilter() {
  var memo = {};
  return function(fn) {
    return function(evt, name) {
      memo[evt + name] = [evt, name];
      setTimeout(function() {
        Object.keys(memo).forEach(function(n) {
          fn.apply(null, memo[n]);
        });
        memo = {};
      });
    }
  }
}

function getSubDirectories(dir, fn, done = function() {}) {
  if (is.directory(dir)) {
    fs.readdir(dir, function(err, all) {
      if (err) {
        // don't throw permission errors.
        if (/^(EPERM|EACCES)$/.test(err.code)) {
          console.warn('Warning: Cannot access %s.', dir);
        } else {
          throw err;
        }
      }
      else {
        all.forEach(function(f) {
          var sdir = path.join(dir, f);
          if (is.directory(sdir)) fn(sdir);
        });
        done();
      }
    });
  } else {
    done();
  }
}

function semaphore(final) {
  var counter = 0;
  return function start() {
    counter++;
    return function stop() {
      counter--;
      if (counter === 0) final();
    };
  };
}

function nullCounter() {
  return function nullStop() {};
}

function shouldNotSkip(filePath, filter) {
  // watch it only if the filter is not function
  // or not being skipped explicitly.
  return !is.func(filter) || filter(filePath, SKIP_FLAG) !== SKIP_FLAG;
}

var deprecationWarning = util.deprecate(
  function() {},
  '(node-watch) First param in callback function\
  is replaced with event name since 0.5.0, use\
  `(evt, filename) => {}` if you want to get the filename'
);

function Watcher() {
  events.EventEmitter.call(this);
  this.watchers = {};
  this._isReady = false;
  this._isClosed = false;
}

util.inherits(Watcher, events.EventEmitter);

Watcher.prototype.expose = function() {
  var expose = {};
  var self = this;
  var methods = [
    'on', 'emit', 'once',
    'close', 'isClosed',
    'listeners', 'setMaxListeners', 'getMaxListeners',
    'getWatchedPaths'
  ];
  methods.forEach(function(name) {
    expose[name] = function() {
      return self[name].apply(self, arguments);
    }
  });
  return expose;
}

Watcher.prototype.isClosed = function() {
  return this._isClosed;
}

Watcher.prototype.close = function(fullPath) {
  var self = this;
  if (fullPath) {
    var watcher = this.watchers[fullPath];
    if (watcher && watcher.close) {
      watcher.close();
      delete self.watchers[fullPath];
    }
    getSubDirectories(fullPath, function(fpath) {
      self.close(fpath);
    });
  }
  else {
    Object.keys(self.watchers).forEach(function(fpath) {
      var watcher = self.watchers[fpath];
      if (watcher && watcher.close) {
        watcher.close();
      }
    });
    this.watchers = {};
  }
  // Do not close the Watcher unless all child watchers are closed.
  // https://github.com/yuanchuan/node-watch/issues/75
  if (is.emptyObject(self.watchers)) {
    // should emit once
    if (!this._isClosed) {
      this._isClosed = true;
      process.nextTick(emitClose, this);
    }
  }
}

Watcher.prototype.getWatchedPaths = function(fn) {
  if (is.func(fn)) {
    var self = this;
    if (self._isReady) {
      fn(Object.keys(self.watchers));
    } else {
      self.on('ready', function() {
        fn(Object.keys(self.watchers));
      });
    }
  }
}

function emitReady(self) {
  if (!self._isReady) {
    self._isReady = true;
    // do not call emit for 'ready' until after watch() has returned,
    // so that consumer can call on().
    process.nextTick(function () {
      self.emit('ready');
    });
  }
}

function emitClose(self) {
  self.emit('close');
}

Watcher.prototype.add = function(watcher, info) {
  var self = this;
  info = info || { fpath: '' };
  var watcherPath = path.resolve(info.fpath);
  this.watchers[watcherPath] = watcher;

  // Internal callback for handling fs.FSWatcher 'change' events
  var internalOnChange = function(rawEvt, rawName) {
    if (self.isClosed()) {
      return;
    }

    // normalise lack of name and convert to full path
    var name = rawName;
    if (is.nil(name)) {
      name = '';
    }
    name = path.join(info.fpath, name);

    if (info.options.recursive) {
      hasNativeRecursive(function(has) {
        if (!has) {
          var fullPath = path.resolve(name);
          // remove watcher on removal
          if (!is.exists(name)) {
            self.close(fullPath);
          }
          // watch new created directory
          else {
            var shouldWatch = is.directory(name)
              && !self.watchers[fullPath]
              && shouldNotSkip(name, info.options.filter);

            if (shouldWatch) {
              self.watchDirectory(name, info.options);
            }
          }
        }
      });
    }

    handlePublicEvents(rawEvt, name);
  };

  // Debounced based on the 'delay' option
  var handlePublicEvents = debounce(info, function (evt, name) {
    // watch single file
    if (info.compareName) {
      if (info.compareName(name)) {
        self.emit('change', evt, name);
      }
    }
    // watch directory
    else {
      var filterGuard = guard(info.options.filter);
      filterGuard(name, function() {
        if (self.flag) self.flag = '';
        else self.emit('change', evt, name);
      });
    }
  });

  watcher.on('error', function(err) {
    if (self.isClosed()) {
      return;
    }
    if (is.windows() && err.code === 'EPERM') {
      watcher.emit('change', EVENT_REMOVE, info.fpath && '');
      self.flag = 'windows-error';
      self.close(watcherPath);
    } else {
      self.emit('error', err);
    }
  });

  watcher.on('change', internalOnChange);
}

Watcher.prototype.watchFile = function(file, options, fn) {
  var parent = path.join(file, '../');
  var opts = Object.assign({}, options, {
    // no filter for single file
    filter: null,
    encoding: 'utf8'
  });

  // no need to watch recursively
  delete opts.recursive;

  var watcher = fs.watch(parent, opts);
  this.add(watcher, {
    type: 'file',
    fpath: parent,
    options: Object.assign({}, opts, {
      encoding: options.encoding
    }),
    compareName: function(n) {
      return is.samePath(n, file);
    }
  });

  if (is.func(fn)) {
    if (fn.length === 1) deprecationWarning();
    this.on('change', fn);
  }
}

Watcher.prototype.watchDirectory = function(dir, options, fn, counter = nullCounter) {
  var self = this;
  var done = counter();
  hasNativeRecursive(function(has) {
    // always specify recursive
    options.recursive = !!options.recursive;
    // using utf8 internally
    var opts = Object.assign({}, options, {
      encoding: 'utf8'
    });
    if (!has) {
      delete opts.recursive;
    }

    // check if it's closed before calling watch.
    if (self._isClosed) {
      done();
      return self.close();
    }

    var watcher = fs.watch(dir, opts);

    self.add(watcher, {
      type: 'dir',
      fpath: dir,
      options: options
    });

    if (is.func(fn)) {
      if (fn.length === 1) deprecationWarning();
      self.on('change', fn);
    }

    if (options.recursive && !has) {
      getSubDirectories(dir, function(d) {
        if (shouldNotSkip(d, options.filter)) {
          self.watchDirectory(d, options, null, counter);
        }
      }, counter());
    }

    done();
  });
}

function composeWatcher(watchers) {
  var watcher = new Watcher();
  var filterDups = createDupsFilter();
  var counter = watchers.length;

  watchers.forEach(function(w) {
    w.on('change', filterDups(function(evt, name) {
      watcher.emit('change', evt, name);
    }));
    w.on('error', function(err) {
      watcher.emit('error', err);
    });
    w.on('ready', function() {
      if (!(--counter)) {
        emitReady(watcher);
      }
    });
  });

  watcher.close = function() {
    watchers.forEach(function(w) {
      w.close();
    });
    process.nextTick(emitClose, watcher);
  }

  watcher.getWatchedPaths = function(fn) {
    if (is.func(fn)) {
      var promises = watchers.map(function(w) {
        return new Promise(function(resolve) {
          w.getWatchedPaths(resolve);
        });
      });
      Promise.all(promises).then(function(result) {
        var ret = unique(flat1(result));
        fn(ret);
      });
    }
  }

  return watcher.expose();
}

function watch(fpath, options, fn) {
  var watcher = new Watcher();

  if (is.buffer(fpath)) {
    fpath = fpath.toString();
  }

  if (!is.array(fpath) && !is.exists(fpath)) {
    watcher.emit('error',
      new Error(fpath + ' does not exist.')
    );
  }

  if (is.string(options)) {
    options = {
      encoding: options
    }
  }

  if (is.func(options)) {
    fn = options;
    options = {};
  }

  if (arguments.length < 2) {
    options = {};
  }

  if (options.encoding) {
    assertEncoding(options.encoding);
  } else {
    options.encoding = 'utf8';
  }

  if (is.array(fpath)) {
    if (fpath.length === 1) {
      return watch(fpath[0], options, fn);
    }
    var filterDups = createDupsFilter();
    return composeWatcher(unique(fpath).map(function(f) {
      var w = watch(f, options);
      if (is.func(fn)) {
        w.on('change', filterDups(fn));
      }
      return w;
    }));
  }

  if (is.file(fpath)) {
    watcher.watchFile(fpath, options, fn);
    emitReady(watcher);
  }

  else if (is.directory(fpath)) {
    var counter = semaphore(function () {
      emitReady(watcher);
    });
    watcher.watchDirectory(fpath, options, fn, counter);
  }

  return watcher.expose();
}

module.exports = watch;
module.exports.default = watch;
<commit_msg>Format code inside guard function (#119)<commit_after>var fs = require('fs');
var path = require('path');
var util = require('util');
var events = require('events');

var hasNativeRecursive = require('./has-native-recursive');
var is = require('./is');

var EVENT_UPDATE = 'update';
var EVENT_REMOVE = 'remove';

var SKIP_FLAG = Symbol('skip');

function hasDup(arr) {
  return arr.some(function(v, i, self) {
    return self.indexOf(v) !== i;
  });
}

function unique(arr) {
  return arr.filter(function(v, i, self) {
    return self.indexOf(v) === i;
  });
}

// One level flat
function flat1(arr) {
  return arr.reduce(function(acc, v) {
    return acc.concat(v);
  }, []);
}

function assertEncoding(encoding) {
  if (encoding && encoding !== 'buffer' && !Buffer.isEncoding(encoding)) {
    throw new Error('Unknown encoding: ' + encoding);
  }
}

function guard(fn) {
  if (is.func(fn)) {
    return function(arg, action) {
      if (fn(arg, false)) action();
    }
  }
  if (is.regExp(fn)) {
    return function(arg, action) {
      if (fn.test(arg)) action();
    }
  }
  return function(arg, action) {
    action();
  }
}

function composeMessage(names) {
  return names.map(function(n) {
    return is.exists(n)
      ? [EVENT_UPDATE, n]
      : [EVENT_REMOVE, n];
  });
}

function getMessages(cache) {
  var filtered = unique(cache);

  // Saving file from an editor? If so, assuming the
  // non-existed files in the cache are temporary files
  // generated by an editor and thus be filtered.
  var reg = /~$|^\.#|^##$/g;
  var hasSpecialChar = cache.some(function(c) {
    return reg.test(c);
  });

  if (hasSpecialChar) {
    var dup = hasDup(cache.map(function(c) {
      return c.replace(reg, '');
    }));
    if (dup) {
      filtered = filtered.filter(function(m) {
        return is.exists(m);
      });
    }
  }

  return composeMessage(filtered);
}

function debounce(info, fn) {
  var timer, cache = [];
  var encoding = info.options.encoding;
  var delay = info.options.delay;
  if (!is.number(delay)) {
    delay = 200;
  }
  function handle() {
    getMessages(cache).forEach(function(msg) {
      msg[1] = Buffer.from(msg[1]);
      if (encoding !== 'buffer') {
        msg[1] = msg[1].toString(encoding);
      }
      fn.apply(null, msg);
    });
    timer = null;
    cache = [];
  }
  return function(rawEvt, name) {
    cache.push(name);
    if (!timer) {
      timer = setTimeout(handle, delay);
    }
  }
}

function createDupsFilter() {
  var memo = {};
  return function(fn) {
    return function(evt, name) {
      memo[evt + name] = [evt, name];
      setTimeout(function() {
        Object.keys(memo).forEach(function(n) {
          fn.apply(null, memo[n]);
        });
        memo = {};
      });
    }
  }
}

function getSubDirectories(dir, fn, done = function() {}) {
  if (is.directory(dir)) {
    fs.readdir(dir, function(err, all) {
      if (err) {
        // don't throw permission errors.
        if (/^(EPERM|EACCES)$/.test(err.code)) {
          console.warn('Warning: Cannot access %s.', dir);
        } else {
          throw err;
        }
      }
      else {
        all.forEach(function(f) {
          var sdir = path.join(dir, f);
          if (is.directory(sdir)) fn(sdir);
        });
        done();
      }
    });
  } else {
    done();
  }
}

function semaphore(final) {
  var counter = 0;
  return function start() {
    counter++;
    return function stop() {
      counter--;
      if (counter === 0) final();
    };
  };
}

function nullCounter() {
  return function nullStop() {};
}

function shouldNotSkip(filePath, filter) {
  // watch it only if the filter is not function
  // or not being skipped explicitly.
  return !is.func(filter) || filter(filePath, SKIP_FLAG) !== SKIP_FLAG;
}

var deprecationWarning = util.deprecate(
  function() {},
  '(node-watch) First param in callback function\
  is replaced with event name since 0.5.0, use\
  `(evt, filename) => {}` if you want to get the filename'
);

function Watcher() {
  events.EventEmitter.call(this);
  this.watchers = {};
  this._isReady = false;
  this._isClosed = false;
}

util.inherits(Watcher, events.EventEmitter);

Watcher.prototype.expose = function() {
  var expose = {};
  var self = this;
  var methods = [
    'on', 'emit', 'once',
    'close', 'isClosed',
    'listeners', 'setMaxListeners', 'getMaxListeners',
    'getWatchedPaths'
  ];
  methods.forEach(function(name) {
    expose[name] = function() {
      return self[name].apply(self, arguments);
    }
  });
  return expose;
}

Watcher.prototype.isClosed = function() {
  return this._isClosed;
}

Watcher.prototype.close = function(fullPath) {
  var self = this;
  if (fullPath) {
    var watcher = this.watchers[fullPath];
    if (watcher && watcher.close) {
      watcher.close();
      delete self.watchers[fullPath];
    }
    getSubDirectories(fullPath, function(fpath) {
      self.close(fpath);
    });
  }
  else {
    Object.keys(self.watchers).forEach(function(fpath) {
      var watcher = self.watchers[fpath];
      if (watcher && watcher.close) {
        watcher.close();
      }
    });
    this.watchers = {};
  }
  // Do not close the Watcher unless all child watchers are closed.
  // https://github.com/yuanchuan/node-watch/issues/75
  if (is.emptyObject(self.watchers)) {
    // should emit once
    if (!this._isClosed) {
      this._isClosed = true;
      process.nextTick(emitClose, this);
    }
  }
}

Watcher.prototype.getWatchedPaths = function(fn) {
  if (is.func(fn)) {
    var self = this;
    if (self._isReady) {
      fn(Object.keys(self.watchers));
    } else {
      self.on('ready', function() {
        fn(Object.keys(self.watchers));
      });
    }
  }
}

function emitReady(self) {
  if (!self._isReady) {
    self._isReady = true;
    // do not call emit for 'ready' until after watch() has returned,
    // so that consumer can call on().
    process.nextTick(function () {
      self.emit('ready');
    });
  }
}

function emitClose(self) {
  self.emit('close');
}

Watcher.prototype.add = function(watcher, info) {
  var self = this;
  info = info || { fpath: '' };
  var watcherPath = path.resolve(info.fpath);
  this.watchers[watcherPath] = watcher;

  // Internal callback for handling fs.FSWatcher 'change' events
  var internalOnChange = function(rawEvt, rawName) {
    if (self.isClosed()) {
      return;
    }

    // normalise lack of name and convert to full path
    var name = rawName;
    if (is.nil(name)) {
      name = '';
    }
    name = path.join(info.fpath, name);

    if (info.options.recursive) {
      hasNativeRecursive(function(has) {
        if (!has) {
          var fullPath = path.resolve(name);
          // remove watcher on removal
          if (!is.exists(name)) {
            self.close(fullPath);
          }
          // watch new created directory
          else {
            var shouldWatch = is.directory(name)
              && !self.watchers[fullPath]
              && shouldNotSkip(name, info.options.filter);

            if (shouldWatch) {
              self.watchDirectory(name, info.options);
            }
          }
        }
      });
    }

    handlePublicEvents(rawEvt, name);
  };

  // Debounced based on the 'delay' option
  var handlePublicEvents = debounce(info, function (evt, name) {
    // watch single file
    if (info.compareName) {
      if (info.compareName(name)) {
        self.emit('change', evt, name);
      }
    }
    // watch directory
    else {
      var filterGuard = guard(info.options.filter);
      filterGuard(name, function() {
        if (self.flag) self.flag = '';
        else self.emit('change', evt, name);
      });
    }
  });

  watcher.on('error', function(err) {
    if (self.isClosed()) {
      return;
    }
    if (is.windows() && err.code === 'EPERM') {
      watcher.emit('change', EVENT_REMOVE, info.fpath && '');
      self.flag = 'windows-error';
      self.close(watcherPath);
    } else {
      self.emit('error', err);
    }
  });

  watcher.on('change', internalOnChange);
}

Watcher.prototype.watchFile = function(file, options, fn) {
  var parent = path.join(file, '../');
  var opts = Object.assign({}, options, {
    // no filter for single file
    filter: null,
    encoding: 'utf8'
  });

  // no need to watch recursively
  delete opts.recursive;

  var watcher = fs.watch(parent, opts);
  this.add(watcher, {
    type: 'file',
    fpath: parent,
    options: Object.assign({}, opts, {
      encoding: options.encoding
    }),
    compareName: function(n) {
      return is.samePath(n, file);
    }
  });

  if (is.func(fn)) {
    if (fn.length === 1) deprecationWarning();
    this.on('change', fn);
  }
}

Watcher.prototype.watchDirectory = function(dir, options, fn, counter = nullCounter) {
  var self = this;
  var done = counter();
  hasNativeRecursive(function(has) {
    // always specify recursive
    options.recursive = !!options.recursive;
    // using utf8 internally
    var opts = Object.assign({}, options, {
      encoding: 'utf8'
    });
    if (!has) {
      delete opts.recursive;
    }

    // check if it's closed before calling watch.
    if (self._isClosed) {
      done();
      return self.close();
    }

    var watcher = fs.watch(dir, opts);

    self.add(watcher, {
      type: 'dir',
      fpath: dir,
      options: options
    });

    if (is.func(fn)) {
      if (fn.length === 1) deprecationWarning();
      self.on('change', fn);
    }

    if (options.recursive && !has) {
      getSubDirectories(dir, function(d) {
        if (shouldNotSkip(d, options.filter)) {
          self.watchDirectory(d, options, null, counter);
        }
      }, counter());
    }

    done();
  });
}

function composeWatcher(watchers) {
  var watcher = new Watcher();
  var filterDups = createDupsFilter();
  var counter = watchers.length;

  watchers.forEach(function(w) {
    w.on('change', filterDups(function(evt, name) {
      watcher.emit('change', evt, name);
    }));
    w.on('error', function(err) {
      watcher.emit('error', err);
    });
    w.on('ready', function() {
      if (!(--counter)) {
        emitReady(watcher);
      }
    });
  });

  watcher.close = function() {
    watchers.forEach(function(w) {
      w.close();
    });
    process.nextTick(emitClose, watcher);
  }

  watcher.getWatchedPaths = function(fn) {
    if (is.func(fn)) {
      var promises = watchers.map(function(w) {
        return new Promise(function(resolve) {
          w.getWatchedPaths(resolve);
        });
      });
      Promise.all(promises).then(function(result) {
        var ret = unique(flat1(result));
        fn(ret);
      });
    }
  }

  return watcher.expose();
}

function watch(fpath, options, fn) {
  var watcher = new Watcher();

  if (is.buffer(fpath)) {
    fpath = fpath.toString();
  }

  if (!is.array(fpath) && !is.exists(fpath)) {
    watcher.emit('error',
      new Error(fpath + ' does not exist.')
    );
  }

  if (is.string(options)) {
    options = {
      encoding: options
    }
  }

  if (is.func(options)) {
    fn = options;
    options = {};
  }

  if (arguments.length < 2) {
    options = {};
  }

  if (options.encoding) {
    assertEncoding(options.encoding);
  } else {
    options.encoding = 'utf8';
  }

  if (is.array(fpath)) {
    if (fpath.length === 1) {
      return watch(fpath[0], options, fn);
    }
    var filterDups = createDupsFilter();
    return composeWatcher(unique(fpath).map(function(f) {
      var w = watch(f, options);
      if (is.func(fn)) {
        w.on('change', filterDups(fn));
      }
      return w;
    }));
  }

  if (is.file(fpath)) {
    watcher.watchFile(fpath, options, fn);
    emitReady(watcher);
  }

  else if (is.directory(fpath)) {
    var counter = semaphore(function () {
      emitReady(watcher);
    });
    watcher.watchDirectory(fpath, options, fn, counter);
  }

  return watcher.expose();
}

module.exports = watch;
module.exports.default = watch;
<|endoftext|>


<commit_before>var fs   = require('fs');
var path = require('path');
var os = require('os');
var util = require('util');
var events = require('events');

var hasNativeRecursive = require('./has-native-recursive');
var is = require('./is');

var EVENT_UPDATE = 'update';
var EVENT_REMOVE = 'remove';

function hasDup(arr) {
  return arr.some(function(v, i, self) {
    return self.indexOf(v) !== i;
  });
}

function unique(arr) {
  return arr.filter(function(v, i, self) {
    return self.indexOf(v) === i;
  });
}

function assertEncoding(encoding) {
  if (encoding && encoding !== 'buffer' && !Buffer.isEncoding(encoding)) {
    throw new Error('Unknown encoding: ' + encoding);
  }
}

function guard(fn) {
  return function(arg, action) {
    if (is.func(fn)) {
      if (fn(arg)) action();
    }
    else if (is.regExp(fn)) {
      if (fn.test(arg)) action();
    }
    else {
      action();
    }
  }
}

function composeMessage(names) {
  return names.map(function(n) {
    return is.exists(n)
      ? [EVENT_UPDATE, n]
      : [EVENT_REMOVE, n];
  });
}

function getMessages(cache) {
  var dup = hasDup(cache.map(function(c) {
    return c.replace(/^[~#]+|[~#]+$/, '');
  }));

  // saving file from an editor maybe?
  if (dup) {
    var filtered = cache.filter(function(m) {
      return is.exists(m)
    });
    return composeMessage(unique(filtered));
  }

  return composeMessage(cache);
}

function debounce(info, fn) {
  var timer, cache = [];
  var encoding = info.options.encoding;
  var delay = info.options.delay;
  if (!is.number(delay)) {
    delay = 200;
  }
  function handle() {
    getMessages(cache).forEach(function(msg) {
      msg[1] = Buffer.from(msg[1]);
      if (encoding !== 'buffer') {
        msg[1] = msg[1].toString(encoding);
      }
      fn.apply(null, msg);
    });
    timer = null;
    cache = [];
  }
  return function(rawEvt, name) {
    cache.push(name);
    if (!timer) {
      timer = setTimeout(handle, delay);
    }
  }
}

function createDupsFilter() {
  var memo = {};
  return function(fn) {
    return function(evt, name) {
      memo[evt + name] = [evt, name];
      setTimeout(function() {
        Object.keys(memo).forEach(function(n) {
          fn.apply(null, memo[n]);
        });
        memo = {};
      });
    }
  }
}

function getSubDirectories(dir, fn, done = function() {}) {
  if (is.directory(dir)) {
    fs.readdir(dir, function(err, all) {
      if (err) {
        // don't throw permission errors.
        if (!/^(EPERM|EACCES)$/.test(err.code)) throw err;
        else console.warn('Warning: Cannot access %s.', dir);
      }
      else {
        all.forEach(function(f) {
          var sdir = path.join(dir, f);
          if (is.directory(sdir)) fn(sdir);
        });
        done();
      }
    });
  } else {
    done();
  }
}

function semaphore(final) {
  var counter = 0;
  return function start() {
    counter++;
    return function stop() {
      counter--;
      if (counter === 0) final();
    };
  };
}

function nullCounter() {
  return function nullStop() {};
}

var deprecationWarning = util.deprecate(
  function() {},
  '(node-watch) First param in callback function\
  is replaced with event name since 0.5.0, use\
  `(evt, filename) => {}` if you want to get the filename'
);

function Watcher() {
  events.EventEmitter.call(this);
  this.watchers = {};
  this._isReady = false;
  this._isClosed = false;
}

util.inherits(Watcher, events.EventEmitter);

Watcher.prototype.expose = function() {
  var self = this;
  var methods = [
    'on', 'emit', 'close', 'isClosed', 'listeners', 'once',
    'setMaxListeners', 'getMaxListeners'
  ];
  return methods.reduce(function(expose, name) {
    expose[name] = function() {
      return self[name].apply(self, arguments);
    }
    return expose;
  }, {});
}

Watcher.prototype.isClosed = function() {
  return this._isClosed;
}

Watcher.prototype.close = function(fullPath) {
  var self = this;
  if (fullPath) {
    var watcher = this.watchers[fullPath];
    if (watcher && watcher.close) {
      watcher.close();
      delete self.watchers[fullPath];
    }
    getSubDirectories(fullPath, function(fpath) {
      self.close(fpath);
    });
  }
  else {
    Object.keys(self.watchers).forEach(function(fpath) {
      var watcher = self.watchers[fpath];
      if (watcher && watcher.close) {
        watcher.close();
      }
    });
    this.watchers = {};
  }
  // Do not close the Watcher unless all child watchers are closed.
  // https://github.com/yuanchuan/node-watch/issues/75
  if (is.emptyObject(self.watchers)) {
    this._isClosed = true;
    process.nextTick(emitClose, this);
  }
};

function emitReady(self) {
  if (!self._isReady) {
    self._isReady = true;
    // do not call emit for 'ready' until after watch() has returned,
    // so that consumer can call on().
    process.nextTick(function () {
      self.emit('ready');
    });
  }
}

function emitClose(self) {
  self.emit('close');
}

Watcher.prototype.add = function(watcher, info) {
  var self = this;
  info = info || { fpath: '' };
  var watcherPath = path.resolve(info.fpath);
  this.watchers[watcherPath] = watcher;

  // Internal callback for handling fs.FSWatcher 'change' events
  var internalOnChange = function(rawEvt, rawName) {
    if (self.isClosed()) {
      return;
    }

    // normalise lack of name and convert to full path
    var name;
    if (is.nil(rawName)) {
      name = '';
    } else {
      name = rawName;
    }
    name = path.join(info.fpath, name);

    if (info.options.recursive) {
      hasNativeRecursive(function(has) {
        if (!has) {
          var fullPath = path.resolve(name);
          // remove watcher on removal
          if (!is.exists(name)) {
            self.close(fullPath);
          }
          // watch new created directory
          else if (is.directory(name) && !self.watchers[fullPath]) {
            self.watchDirectory(name, info.options);
          }
        }
      });
    }

    handlePublicEvents(rawEvt, name);
  };

  // Debounced based on the 'delay' option
  var handlePublicEvents = debounce(info, function (evt, name) {
    // watch single file
    if (info.compareName) {
      if (info.compareName(name)) {
        self.emit('change', evt, name);
      }
    }
    // watch directory
    else {
      var filterGuard = guard(info.options.filter);
      filterGuard(name, function() {
        if (self.flag) self.flag = '';
        else self.emit('change', evt, name);
      });
    }
  });

  watcher.on('error', function(err) {
    if (self.isClosed()) {
      return;
    }
    if (os.platform() === 'win32' && err.code === 'EPERM') {
      watcher.emit('change', EVENT_REMOVE, info.fpath && '');
      self.flag = 'windows-error';
      self.close(watcherPath);
    } else {
      self.emit('error', err);
    }
  });

  watcher.on('change', internalOnChange);
}

Watcher.prototype.watchFile = function(file, options, fn) {
  var parent = path.join(file, '../');
  var opts = Object.assign({}, options, {
    // no need to watch recursively
    recursive: false,
    // no filter for single file
    filter: null,
    encoding: 'utf8'
  });

  var watcher = fs.watch(parent, opts);
  this.add(watcher, {
    type: 'file',
    fpath: parent,
    options: Object.assign({}, opts, {
      encoding: options.encoding
    }),
    compareName: function(n) {
      return is.sameFile(n, file);
    }
  });

  if (is.func(fn)) {
    if (fn.length === 1) deprecationWarning();
    this.on('change', fn);
  }
}

Watcher.prototype.watchDirectory = function(dir, options, fn, counter = nullCounter) {
  var self = this;
  var done = counter();
  hasNativeRecursive(function(has) {
    // always specify recursive
    options.recursive = !!options.recursive;
    // using utf8 internally
    var opts = Object.assign({}, options, {
      encoding: 'utf8'
    });
    if (!has) {
      Object.assign(opts, { recursive: false });
    }

    var watcher = fs.watch(dir, opts);

    self.add(watcher, {
      type: 'dir',
      fpath: dir,
      options: options
    });

    if (is.func(fn)) {
      if (fn.length === 1) deprecationWarning();
      self.on('change', fn);
    }

    if (options.recursive && !has) {
      getSubDirectories(dir, function(d) {
        self.watchDirectory(d, options, null, counter);
      }, counter());
    }

    done();
  });
}

function composeWatcher(watchers) {
  var watcher = new Watcher();
  var filterDups = createDupsFilter();
  var counter = watchers.length;
  watchers.forEach(function(w) {
    w.on('change', filterDups(function(evt, name) {
      watcher.emit('change', evt, name);
    }));
    w.on('error', function(err) {
      watcher.emit('error', err);
    });
    w.on('ready', function() {
      if (!(--counter)) {
        emitReady(watcher);
      }
    });
  });

  watcher.close = function() {
    watchers.forEach(function(w) {
      w.close();
    });
    process.nextTick(emitClose, watcher);
  }
  return watcher.expose();
}

function watch(fpath, options, fn) {
  var watcher = new Watcher();

  if (is.buffer(fpath)) {
    fpath = fpath.toString();
  }

  if (is.array(fpath)) {
    if (fpath.length === 1) {
      return watch(fpath[0], options, fn);
    }
    var filterDups = createDupsFilter();
    return composeWatcher(unique(fpath).map(function(f) {
      var w = watch(f, options);
      if (fn) w.on('change', filterDups(fn));
      return w;
    }));
  }

  if (!is.exists(fpath)) {
    watcher.emit('error',
      new Error(fpath + ' does not exist.')
    );
  }

  if (is.string(options)) {
    options = {
      encoding: options
    }
  }

  if (is.func(options)) {
    fn = options;
    options = {};
  }

  if (arguments.length < 2) {
    options = {};
  }

  if (options.encoding) {
    assertEncoding(options.encoding);
  } else {
    options.encoding = 'utf8';
  }

  if (is.file(fpath)) {
    watcher.watchFile(fpath, options, fn);
    emitReady(watcher);
  }

  else if (is.directory(fpath)) {
    var counter = semaphore(function () {
      emitReady(watcher);
    });
    watcher.watchDirectory(fpath, options, fn, counter);
  }

  return watcher.expose();
}

module.exports = watch;
<commit_msg>clean up code logic<commit_after>var fs   = require('fs');
var path = require('path');
var os = require('os');
var util = require('util');
var events = require('events');

var hasNativeRecursive = require('./has-native-recursive');
var is = require('./is');

var EVENT_UPDATE = 'update';
var EVENT_REMOVE = 'remove';

function hasDup(arr) {
  return arr.some(function(v, i, self) {
    return self.indexOf(v) !== i;
  });
}

function unique(arr) {
  return arr.filter(function(v, i, self) {
    return self.indexOf(v) === i;
  });
}

function assertEncoding(encoding) {
  if (encoding && encoding !== 'buffer' && !Buffer.isEncoding(encoding)) {
    throw new Error('Unknown encoding: ' + encoding);
  }
}

function guard(fn) {
  return function(arg, action) {
    if (is.func(fn)) {
      if (fn(arg)) action();
    }
    else if (is.regExp(fn)) {
      if (fn.test(arg)) action();
    }
    else {
      action();
    }
  }
}

function composeMessage(names) {
  return names.map(function(n) {
    return is.exists(n)
      ? [EVENT_UPDATE, n]
      : [EVENT_REMOVE, n];
  });
}

function getMessages(cache) {
  var dup = hasDup(cache.map(function(c) {
    return c.replace(/^[~#]+|[~#]+$/, '');
  }));

  // saving file from an editor maybe?
  if (dup) {
    var filtered = cache.filter(function(m) {
      return is.exists(m)
    });
    return composeMessage(unique(filtered));
  }

  return composeMessage(cache);
}

function debounce(info, fn) {
  var timer, cache = [];
  var encoding = info.options.encoding;
  var delay = info.options.delay;
  if (!is.number(delay)) {
    delay = 200;
  }
  function handle() {
    getMessages(cache).forEach(function(msg) {
      msg[1] = Buffer.from(msg[1]);
      if (encoding !== 'buffer') {
        msg[1] = msg[1].toString(encoding);
      }
      fn.apply(null, msg);
    });
    timer = null;
    cache = [];
  }
  return function(rawEvt, name) {
    cache.push(name);
    if (!timer) {
      timer = setTimeout(handle, delay);
    }
  }
}

function createDupsFilter() {
  var memo = {};
  return function(fn) {
    return function(evt, name) {
      memo[evt + name] = [evt, name];
      setTimeout(function() {
        Object.keys(memo).forEach(function(n) {
          fn.apply(null, memo[n]);
        });
        memo = {};
      });
    }
  }
}

function getSubDirectories(dir, fn, done = function() {}) {
  if (is.directory(dir)) {
    fs.readdir(dir, function(err, all) {
      if (err) {
        // don't throw permission errors.
        if (/^(EPERM|EACCES)$/.test(err.code)) {
          console.warn('Warning: Cannot access %s.', dir);
        } else {
          throw err;
        }
      }
      else {
        all.forEach(function(f) {
          var sdir = path.join(dir, f);
          if (is.directory(sdir)) fn(sdir);
        });
        done();
      }
    });
  } else {
    done();
  }
}

function semaphore(final) {
  var counter = 0;
  return function start() {
    counter++;
    return function stop() {
      counter--;
      if (counter === 0) final();
    };
  };
}

function nullCounter() {
  return function nullStop() {};
}

var deprecationWarning = util.deprecate(
  function() {},
  '(node-watch) First param in callback function\
  is replaced with event name since 0.5.0, use\
  `(evt, filename) => {}` if you want to get the filename'
);

function Watcher() {
  events.EventEmitter.call(this);
  this.watchers = {};
  this._isReady = false;
  this._isClosed = false;
}

util.inherits(Watcher, events.EventEmitter);

Watcher.prototype.expose = function() {
  var self = this;
  var methods = [
    'on', 'emit', 'close', 'isClosed', 'listeners', 'once',
    'setMaxListeners', 'getMaxListeners'
  ];
  return methods.reduce(function(expose, name) {
    expose[name] = function() {
      return self[name].apply(self, arguments);
    }
    return expose;
  }, {});
}

Watcher.prototype.isClosed = function() {
  return this._isClosed;
}

Watcher.prototype.close = function(fullPath) {
  var self = this;
  if (fullPath) {
    var watcher = this.watchers[fullPath];
    if (watcher && watcher.close) {
      watcher.close();
      delete self.watchers[fullPath];
    }
    getSubDirectories(fullPath, function(fpath) {
      self.close(fpath);
    });
  }
  else {
    Object.keys(self.watchers).forEach(function(fpath) {
      var watcher = self.watchers[fpath];
      if (watcher && watcher.close) {
        watcher.close();
      }
    });
    this.watchers = {};
  }
  // Do not close the Watcher unless all child watchers are closed.
  // https://github.com/yuanchuan/node-watch/issues/75
  if (is.emptyObject(self.watchers)) {
    this._isClosed = true;
    process.nextTick(emitClose, this);
  }
};

function emitReady(self) {
  if (!self._isReady) {
    self._isReady = true;
    // do not call emit for 'ready' until after watch() has returned,
    // so that consumer can call on().
    process.nextTick(function () {
      self.emit('ready');
    });
  }
}

function emitClose(self) {
  self.emit('close');
}

Watcher.prototype.add = function(watcher, info) {
  var self = this;
  info = info || { fpath: '' };
  var watcherPath = path.resolve(info.fpath);
  this.watchers[watcherPath] = watcher;

  // Internal callback for handling fs.FSWatcher 'change' events
  var internalOnChange = function(rawEvt, rawName) {
    if (self.isClosed()) {
      return;
    }

    // normalise lack of name and convert to full path
    var name = rawName;
    if (is.nil(name)) {
      name = '';
    }
    name = path.join(info.fpath, name);

    if (info.options.recursive) {
      hasNativeRecursive(function(has) {
        if (!has) {
          var fullPath = path.resolve(name);
          // remove watcher on removal
          if (!is.exists(name)) {
            self.close(fullPath);
          }
          // watch new created directory
          else if (is.directory(name) && !self.watchers[fullPath]) {
            self.watchDirectory(name, info.options);
          }
        }
      });
    }

    handlePublicEvents(rawEvt, name);
  };

  // Debounced based on the 'delay' option
  var handlePublicEvents = debounce(info, function (evt, name) {
    // watch single file
    if (info.compareName) {
      if (info.compareName(name)) {
        self.emit('change', evt, name);
      }
    }
    // watch directory
    else {
      var filterGuard = guard(info.options.filter);
      filterGuard(name, function() {
        if (self.flag) self.flag = '';
        else self.emit('change', evt, name);
      });
    }
  });

  watcher.on('error', function(err) {
    if (self.isClosed()) {
      return;
    }
    if (os.platform() === 'win32' && err.code === 'EPERM') {
      watcher.emit('change', EVENT_REMOVE, info.fpath && '');
      self.flag = 'windows-error';
      self.close(watcherPath);
    } else {
      self.emit('error', err);
    }
  });

  watcher.on('change', internalOnChange);
}

Watcher.prototype.watchFile = function(file, options, fn) {
  var parent = path.join(file, '../');
  var opts = Object.assign({}, options, {
    // no need to watch recursively
    recursive: false,
    // no filter for single file
    filter: null,
    encoding: 'utf8'
  });

  var watcher = fs.watch(parent, opts);
  this.add(watcher, {
    type: 'file',
    fpath: parent,
    options: Object.assign({}, opts, {
      encoding: options.encoding
    }),
    compareName: function(n) {
      return is.sameFile(n, file);
    }
  });

  if (is.func(fn)) {
    if (fn.length === 1) deprecationWarning();
    this.on('change', fn);
  }
}

Watcher.prototype.watchDirectory = function(dir, options, fn, counter = nullCounter) {
  var self = this;
  var done = counter();
  hasNativeRecursive(function(has) {
    // always specify recursive
    options.recursive = !!options.recursive;
    // using utf8 internally
    var opts = Object.assign({}, options, {
      encoding: 'utf8'
    });
    if (!has) {
      Object.assign(opts, { recursive: false });
    }

    var watcher = fs.watch(dir, opts);

    self.add(watcher, {
      type: 'dir',
      fpath: dir,
      options: options
    });

    if (is.func(fn)) {
      if (fn.length === 1) deprecationWarning();
      self.on('change', fn);
    }

    if (options.recursive && !has) {
      getSubDirectories(dir, function(d) {
        self.watchDirectory(d, options, null, counter);
      }, counter());
    }

    done();
  });
}

function composeWatcher(watchers) {
  var watcher = new Watcher();
  var filterDups = createDupsFilter();
  var counter = watchers.length;
  watchers.forEach(function(w) {
    w.on('change', filterDups(function(evt, name) {
      watcher.emit('change', evt, name);
    }));
    w.on('error', function(err) {
      watcher.emit('error', err);
    });
    w.on('ready', function() {
      if (!(--counter)) {
        emitReady(watcher);
      }
    });
  });

  watcher.close = function() {
    watchers.forEach(function(w) {
      w.close();
    });
    process.nextTick(emitClose, watcher);
  }
  return watcher.expose();
}

function watch(fpath, options, fn) {
  var watcher = new Watcher();

  if (is.buffer(fpath)) {
    fpath = fpath.toString();
  }

  if (is.array(fpath)) {
    if (fpath.length === 1) {
      return watch(fpath[0], options, fn);
    }
    var filterDups = createDupsFilter();
    return composeWatcher(unique(fpath).map(function(f) {
      var w = watch(f, options);
      if (fn) w.on('change', filterDups(fn));
      return w;
    }));
  }

  if (!is.exists(fpath)) {
    watcher.emit('error',
      new Error(fpath + ' does not exist.')
    );
  }

  if (is.string(options)) {
    options = {
      encoding: options
    }
  }

  if (is.func(options)) {
    fn = options;
    options = {};
  }

  if (arguments.length < 2) {
    options = {};
  }

  if (options.encoding) {
    assertEncoding(options.encoding);
  } else {
    options.encoding = 'utf8';
  }

  if (is.file(fpath)) {
    watcher.watchFile(fpath, options, fn);
    emitReady(watcher);
  }

  else if (is.directory(fpath)) {
    var counter = semaphore(function () {
      emitReady(watcher);
    });
    watcher.watchDirectory(fpath, options, fn, counter);
  }

  return watcher.expose();
}

module.exports = watch;
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');
var path = require('path');

var Utils = require('./util/utils');

var VERSION = (function () {
  var data = fs.readFileSync(path.join(__dirname, '../package.json')).toString();
  return JSON.parse(data).version;
})();

var chkInitProcess = function (options) {

  var homePath = Utils.getHomePath(options);
  options.home = homePath;
  try {
    if (!fs.existsSync(homePath)) fs.mkdirSync(homePath, parseInt('0766', 8));
    if (!fs.existsSync(homePath + '/' + (options.upload || 'upload'))) fs.mkdirSync(homePath + '/' + (options.upload || 'upload'), parseInt('0766', 8));
  } catch (e) {
    console.log('Error creating xpush directory: ' + e);
  }

  require('../lib/util/logging')({type: options.type, port: options.port, path: homePath});
};

var welcome = function () {

  return [
    "",
    "                         _     ",
    "                        | |    ",
    "   __  ___ __  _   _ ___| |__  ",
    "   \\ \\/ / '_ \\| | | / __| '_ \\ ",
    "    >  <| |_) | |_| \\__ \\ | | |",
    "   /_/\\_\\ .__/ \\__,_|___/_| |_|",
    "        | |                    ",
    "        |_|         V " + VERSION,
    ""
  ].join('\n');
};

var host = Utils.getIP();
var port = 8080;

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }
  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**

 options.server = {}

 certificate  [String] :  If you want to create an HTTPS server, pass in the PEM-encoded certificate and key
 key  [String] :  If you want to create an HTTPS server, pass in the PEM-encoded certificate and key
 formatters  [Object] :  Custom response formatters for res.send()
 log  [Object] :  You can optionally pass in a bunyan instance; not required
 name  [String] :  By default, this will be set in the Server response header, default is restify
 spdy  [Object] :  Any options accepted by node-spdy
 version  [String] :  A default version to set for all routes
 handleUpgrades  [Boolean] :  Hook the upgrade event from the node HTTP server, pushing Connection: Upgrade requests through the regular request handling chain; defaults to false
 httpsServerOptions  [Object] :  Any options accepted by node-https Server. If provided the following restify server options will be ignored: spdy, ca, certificate, key, passphrase, rejectUnauthorized, requestCert and ciphers; however these can all be specified on httpsServerOptions.

 - http://restify.com/#creating-a-server
 - https://github.com/restify/node-restify/blob/master/lib/server.js

 **/

// SESSION SERVER
function createSessionServer(options) {

  host = options.host || host;
  port = options.port || port;
  options['type'] = 'SESSION';

  chkInitProcess(options);

  var restify = require('restify');

  var optionsServer = options.server || {};

  if (!optionsServer.name) optionsServer.name = 'xpush';

  var server = restify.createServer(optionsServer);

  restify.CORS.ALLOW_HEADERS.push('authorization');
  restify.CORS.ALLOW_HEADERS.push('accept');
  restify.CORS.ALLOW_HEADERS.push('sid');
  restify.CORS.ALLOW_HEADERS.push('lang');
  restify.CORS.ALLOW_HEADERS.push('origin');
  restify.CORS.ALLOW_HEADERS.push('withcredentials');
  restify.CORS.ALLOW_HEADERS.push('x-requested-with');

  server.use(restify.CORS({
    origins: ["*"],
    headers: ["Origin", "X-Requested-With", "Content-Type", "Accept", "Authorization"]
  }));

  server.opts(/.*/, function (req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", req.header("Access-Control-Request-Method"));
    res.header("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers"));
    res.send(200);
    return next();
  });

  var uploadPath = path.join(
    options.home,
    options.upload || 'upload'
  );

  server.use(restify.fullResponse())
  server.use(restify.jsonp());
  server.use(restify.bodyParser({
    mapParams: true,
    uploadDir: uploadPath,
    mapFiles: true,
    keepExtensions: true
  }));

  server.on('error', onError);

  var sessionServer = require('./xpush-session-server.js');
  sessionServer.init(server, options, function (err, result) {

    if (err) {
      console.error(err, result);
    } else {
      server.listen(port, function () {
        console.log(welcome(), '"SESSION server" listening at ' + host + ":" + port);
        sessionServer.emit("started", host, port)
      });
    }

  });

  return (sessionServer);
}

// CHANNEL SERVER
function createChannelServer(options) {

  host = options.host = options.host || Utils.getIP();
  port = options.port || 8080;
  options['type'] = 'CHANNEL';

  chkInitProcess(options);

  var restify = require('restify');
  var socketio = require('socket.io');

  var serverOption = options.server || {};

  if (!serverOption.name) serverOption.name = 'xpush';

  if (options.protocol == 'https' && options.ssl) {
    var key = fs.readFileSync(options.ssl.key);
    var cert = fs.readFileSync(options.ssl.cert);

    serverOption.key = key;
    serverOption.certificate = cert;
  } else {
    options.protocol = 'http';
  }

  var server = restify.createServer(serverOption);

  var io = socketio.listen(server.server);

  server.use(restify.CORS());
  server.on('error', onError);

  var channelServer = require('./xpush-channel-server.js');
  channelServer.init(server, io, options, function (err, result) {

    if (err) {
      console.error(err, result);
    } else {
      server.listen(port, function () {
        console.log(welcome(), '"CHANNEL server" listening at ' + host + ":" + port);
        channelServer.emit("started", host, port);
      });
    }

  });
  return (channelServer);
}

/** Main XPUSH Server **/
module.exports.createSessionServer = createSessionServer;
module.exports.createChannelServer = createChannelServer;

/** Additional Manager (optional, only for specific cases) **/
module.exports.createZookeeperClient = function (options) {

  var zookeeper = require('node-zookeeper-client');
  var addr = 'localhost:2181';
  if (options.zookeeper && options.zookeeper.address)  addr = options.zookeeper.address;
  var zkClient = zookeeper.createClient(addr, {retries: 2});

  return zkClient;

};
module.exports.createRedisManager = function (options) {

  var conf = {
    redis: options.redis
  };

  var RedisManager = require('./session-manager/redis-manager');
  return new RedisManager(conf);

};
<commit_msg>add server argument on create session server (not for channel server)<commit_after>'use strict';

var fs = require('fs');
var path = require('path');

var Utils = require('./util/utils');

var VERSION = (function () {
  var data = fs.readFileSync(path.join(__dirname, '../package.json')).toString();
  return JSON.parse(data).version;
})();

var chkInitProcess = function (options) {

  var homePath = Utils.getHomePath(options);
  options.home = homePath;
  try {
    if (!fs.existsSync(homePath)) fs.mkdirSync(homePath, parseInt('0766', 8));
    if (!fs.existsSync(homePath + '/' + (options.upload || 'upload'))) fs.mkdirSync(homePath + '/' + (options.upload || 'upload'), parseInt('0766', 8));
  } catch (e) {
    console.log('Error creating xpush directory: ' + e);
  }

  require('../lib/util/logging')({type: options.type, port: options.port, path: homePath});
};

var welcome = function () {

  return [
    "",
    "                         _     ",
    "                        | |    ",
    "   __  ___ __  _   _ ___| |__  ",
    "   \\ \\/ / '_ \\| | | / __| '_ \\ ",
    "    >  <| |_) | |_| \\__ \\ | | |",
    "   /_/\\_\\ .__/ \\__,_|___/_| |_|",
    "        | |                    ",
    "        |_|         V " + VERSION,
    ""
  ].join('\n');
};

var host = Utils.getIP();
var port = 8080;

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }
  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

// SESSION SERVER
function createSessionServer(options, cb, server) {

  host = options.host || host;
  port = options.port || port;
  options['type'] = 'SESSION';

  chkInitProcess(options);

  // default RESTIFY server (http://restify.com/)
  if (server === undefined) {

    /**

    options.server = {}

    certificate  [String] :  If you want to create an HTTPS server, pass in the PEM-encoded certificate and key
    key  [String] :  If you want to create an HTTPS server, pass in the PEM-encoded certificate and key
    formatters  [Object] :  Custom response formatters for res.send()
    log  [Object] :  You can optionally pass in a bunyan instance; not required
    name  [String] :  By default, this will be set in the Server response header, default is restify
    spdy  [Object] :  Any options accepted by node-spdy
    version  [String] :  A default version to set for all routes
    handleUpgrades  [Boolean] :  Hook the upgrade event from the node HTTP server, pushing Connection: Upgrade requests through the regular request handling chain; defaults to false
    httpsServerOptions  [Object] :  Any options accepted by node-https Server. If provided the following restify server options will be ignored: spdy, ca, certificate, key, passphrase, rejectUnauthorized, requestCert and ciphers; however these can all be specified on httpsServerOptions.

    - http://restify.com/#creating-a-server
    - https://github.com/restify/node-restify/blob/master/lib/server.js

    **/

    var restify = require('restify');
    var optionsServer = options.server || {};
    if (!optionsServer.name) optionsServer.name = 'xpush';

    var server = restify.createServer(optionsServer);

    restify.CORS.ALLOW_HEADERS.push('authorization');
    restify.CORS.ALLOW_HEADERS.push('accept');
    restify.CORS.ALLOW_HEADERS.push('sid');
    restify.CORS.ALLOW_HEADERS.push('lang');
    restify.CORS.ALLOW_HEADERS.push('origin');
    restify.CORS.ALLOW_HEADERS.push('withcredentials');
    restify.CORS.ALLOW_HEADERS.push('x-requested-with');

    server.use(restify.CORS({
        origins: ["*"],
        headers: ["Origin", "X-Requested-With", "Content-Type", "Accept", "Authorization"]
    }));

    server.opts(/.*/, function (req, res, next) {
        res.header("Access-Control-Allow-Origin", "*");
        res.header("Access-Control-Allow-Methods", req.header("Access-Control-Request-Method"));
        res.header("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers"));
        res.send(200);
        return next();
    });

    var uploadPath = path.join(
      options.home,
      options.upload || 'upload'
    );

    server.use(restify.fullResponse())
    server.use(restify.jsonp());
    server.use(restify.bodyParser({
        mapParams: true,
        uploadDir: uploadPath,
        mapFiles: true,
        keepExtensions: true
    }));

    server.on('error', onError);
    console.log('default server type : RESTIFY ');

  }

  var sessionServer = require('./xpush-session-server.js');
  sessionServer.init(server, options, function (err, result) {

    if (err) {
      console.error(err, result);
      if(cb) cb(err, result);
    } else {
      server.listen(port, function () {

        console.log(welcome(), '"SESSION server" listening at ' + host + ":" + port);

        sessionServer.emit("started", host, port)

        if(cb) cb(err, { "host": host, "port": port });

      });
    }

  });

  return (sessionServer);
}

// CHANNEL SERVER
function createChannelServer(options, cb) {

  host = options.host = options.host || Utils.getIP();
  port = options.port || 8080;
  options['type'] = 'CHANNEL';

  chkInitProcess(options);

  var restify = require('restify');
  var socketio = require('socket.io');

  var serverOption = options.server || {};

  if (!serverOption.name) serverOption.name = 'xpush';

  if (options.protocol == 'https' && options.ssl) {
    var key = fs.readFileSync(options.ssl.key);
    var cert = fs.readFileSync(options.ssl.cert);

    serverOption.key = key;
    serverOption.certificate = cert;
  } else {
    options.protocol = 'http';
  }

  var server = restify.createServer(serverOption);

  var io = socketio.listen(server.server);

  server.use(restify.CORS());
  server.on('error', onError);

  var channelServer = require('./xpush-channel-server.js');
  channelServer.init(server, io, options, function (err, result) {

    if (err) {
      console.error(err, result);
      if(cb) cb(err, result);
    } else {
      server.listen(port, function () {

        console.log(welcome(), '"CHANNEL server" listening at ' + host + ":" + port);

        channelServer.emit("started", host, port);

        if(cb) cb(err, { "host": host, "port": port });

      });

    }

  });
  return (channelServer);
}

/** Main XPUSH Server **/
module.exports.createSessionServer = createSessionServer;
module.exports.createChannelServer = createChannelServer;

/** Additional Manager (optional, only for specific cases) **/
module.exports.createZookeeperClient = function (options) {

  var zookeeper = require('node-zookeeper-client');
  var addr = 'localhost:2181';
  if (options.zookeeper && options.zookeeper.address)  addr = options.zookeeper.address;
  var zkClient = zookeeper.createClient(addr, {retries: 2});

  return zkClient;

};
module.exports.createRedisManager = function (options) {

  var conf = {
    redis: options.redis
  };

  var RedisManager = require('./session-manager/redis-manager');
  return new RedisManager(conf);

};
<|endoftext|>