<commit_before>var http = require('http');
var ws = require('ws');
var path = require('path');
var events = require('events');
var util = require('util');
var async = require('async');
var url = require('url');

var ChromeConnection = (function() {
  function ChromeConnection(port, host) {
    events.EventEmitter.call(this);

    this._port = port;
    this._host = host;

    this._callbacks = [];
    this._counter = 0;

    this._scripts = [];
    this._console = new events.EventEmitter();
  }

  util.inherits(ChromeConnection, events.EventEmitter);

  Object.defineProperty(ChromeConnection.prototype, 'console', {
    get: function() {
      return this._console;
    },
  });

  ChromeConnection.prototype._send = function(method, params, callback) {
    var id = this._counter++;

    var message = {
      id: id,
      method: method,
      params: params
    };

    this._callbacks[id] = callback;

    try {
      this._socket.send(JSON.stringify(message));
    } catch (error) {
      return callback(error);
    }
  };

  ChromeConnection.prototype._process = function(message) {
    if (message.id !== undefined) {
      var callback = this._callbacks[message.id];

      if (callback) {
        if (message.error) {
          callback(message.error);
        } else if (message.params) {
          callback(null, message.params);
        } else if (message.result) {
          callback(null, message.result);
        }

        delete this._callbacks[message.id];
      }
    } else {
      if (message.method == 'Debugger.scriptParsed') {
        this._scripts.push(message.params);
      } else if (message.method == 'Console.messageAdded') {
        this._console.emit('data', message.params.message);
      }
    }
  };

  ChromeConnection.prototype.targets = function(callback) {
    var options = {
      host: this._host,
      port: this._port,
      path: '/json'
    };

    var self = this;
    var request = http.get(options, function(response) {
      var data = '';

      response.on('data', function(chunk) {
        data += chunk;
      });

      response.on('end', function() {
        var targets = JSON.parse(data);
        callback(targets);
      });
    });

    request.on('error', function(error) {
      self.emit('error', error);
    });
  };

  ChromeConnection.prototype.attach = function attach(target) {
    var socket = ws.connect(target.webSocketDebuggerUrl);

    var self = this;
    var send = self._send.bind(self);

    socket.on('open', function() {
      async.series([
        async.apply(send, 'Debugger.enable', {}),
        async.apply(send, 'Runtime.enable', {}),
        async.apply(send, 'Console.enable', {}),
      ], function(error) {
        if (error) {
          return self.emit('error', error);
        }

        self.emit('attach', target);
      });
    });

    socket.on('close', function() {
      self.emit('detatch', target);
    });

    socket.on('error', function(error) {
      self.emit('error', error);
    });

    socket.on('message', function(data) {
      try {
        var message = JSON.parse(data);
        self._process(message);
      } catch (error) {
        self.emit('error', error);
      }
    });

    this._socket = socket;
  };

  ChromeConnection.prototype.evaluate = function evaluate(expression, callback) {
    var params = {
      expression: expression,
    };

    this._send('Runtime.evaluate', params, function evaluate(error, params) {
      if (error) {
        return callback(error);
      }

      callback(null, params.result);
    });
  };

  ChromeConnection.prototype.source = function source(filename, contents, callback) {
    var script = this._scripts.filter(function(src) {
      var location = url.parse(src.url);
      return path.relative('/', location.path || '') === filename;
    })[0];

    if (script === undefined) {
      var error = Error();
      return callback('Unknown script ' + filename);
    }

    var params = {
      scriptId: script.scriptId,
      scriptSource: contents
    };

    this._send('Debugger.setScriptSource', params, function(error, params) {
      if (error) {
        return callback(error);
      }

      callback(null, params);
    });
  };

  return ChromeConnection;
}());

function connect(port, host, callback) {
  var client = new ChromeConnection(port, host);

  if (callback) {
    client.on('attach', callback);
  }

  return client;
}

module.exports.connect = connect;
<commit_msg>Add scripts method to yield the currently loaded scripts<commit_after>var http = require('http');
var ws = require('ws');
var path = require('path');
var events = require('events');
var util = require('util');
var async = require('async');
var url = require('url');

var ChromeConnection = (function() {
  function ChromeConnection(port, host) {
    events.EventEmitter.call(this);

    this._port = port;
    this._host = host;

    this._callbacks = [];
    this._counter = 0;

    this._scripts = [];
    this._console = new events.EventEmitter();
  }

  util.inherits(ChromeConnection, events.EventEmitter);

  Object.defineProperty(ChromeConnection.prototype, 'console', {
    get: function() {
      return this._console;
    },
  });

  ChromeConnection.prototype._send = function(method, params, callback) {
    var id = this._counter++;

    var message = {
      id: id,
      method: method,
      params: params
    };

    this._callbacks[id] = callback;

    try {
      this._socket.send(JSON.stringify(message));
    } catch (error) {
      return callback(error);
    }
  };

  ChromeConnection.prototype._process = function(message) {
    if (message.id !== undefined) {
      var callback = this._callbacks[message.id];

      if (callback) {
        if (message.error) {
          callback(message.error);
        } else if (message.params) {
          callback(null, message.params);
        } else if (message.result) {
          callback(null, message.result);
        }

        delete this._callbacks[message.id];
      }
    } else {
      if (message.method == 'Debugger.scriptParsed') {
        this._scripts.push(message.params);
      } else if (message.method == 'Console.messageAdded') {
        this._console.emit('data', message.params.message);
      }
    }
  };

  ChromeConnection.prototype.targets = function(callback) {
    var options = {
      host: this._host,
      port: this._port,
      path: '/json'
    };

    var self = this;
    var request = http.get(options, function(response) {
      var data = '';

      response.on('data', function(chunk) {
        data += chunk;
      });

      response.on('end', function() {
        var targets = JSON.parse(data);
        callback(targets);
      });
    });

    request.on('error', function(error) {
      self.emit('error', error);
    });
  };

  ChromeConnection.prototype.attach = function attach(target) {
    var socket = ws.connect(target.webSocketDebuggerUrl);

    var self = this;
    var send = self._send.bind(self);

    socket.on('open', function() {
      async.series([
        async.apply(send, 'Debugger.enable', {}),
        async.apply(send, 'Runtime.enable', {}),
        async.apply(send, 'Console.enable', {}),
      ], function(error) {
        if (error) {
          return self.emit('error', error);
        }

        self.emit('attach', target);
      });
    });

    socket.on('close', function() {
      self.emit('detatch', target);
    });

    socket.on('error', function(error) {
      self.emit('error', error);
    });

    socket.on('message', function(data) {
      try {
        var message = JSON.parse(data);
        self._process(message);
      } catch (error) {
        self.emit('error', error);
      }
    });

    this._socket = socket;
  };

  ChromeConnection.prototype.evaluate = function evaluate(expression, callback) {
    var params = {
      expression: expression,
    };

    this._send('Runtime.evaluate', params, function evaluate(error, params) {
      if (error) {
        return callback(error);
      }

      callback(null, params.result);
    });
  };

  ChromeConnection.prototype.scripts = function scripts(callback) {
    callback(this._scripts);
  };

    var params = {
      scriptId: script.scriptId,
      scriptSource: contents
    };

    this._send('Debugger.setScriptSource', params, function(error, params) {
      if (error) {
        return callback(error);
      }

      callback(null, params);
    });
  };

  return ChromeConnection;
}());

function connect(port, host, callback) {
  var client = new ChromeConnection(port, host);

  if (callback) {
    client.on('attach', callback);
  }

  return client;
}

module.exports.connect = connect;
<|endoftext|>


<commit_before>var WebKit = require('webkitgtk');
var Pool = require('generic-pool').Pool;
var fs = require('fs');
var queue = require('queue-async');
var escapeStringRegexp = require('escape-string-regexp');
var request = require('request');
var Path = require('path');

var Dom = module.exports = function(model, options) {
	// init pool later, allowing user to set pool settings
	if (!Dom.pool) Dom.pool = initPool(Dom.settings);
	var h = new Handler(model, options);
	return h.chainable;
};

Dom.Handler = Handler;

Dom.settings = {
	min: 2,
	max: 16,
	busyTimeout: 0,
	idleTimeoutMillis: 1000000,
	refreshIdle: false,
	display: process.env.DISPLAY || 0,
	style: fs.readFileSync(__dirname + '/index.css'),
	debug: !!process.env.DEBUG
};
Dom.plugins = require('./plugins');

Dom.authors = [];
Dom.users = [Dom.plugins.nostylesheets];

Dom.author = function(mw) {
	Dom.authors.push(mw);
	return Dom;
};

Dom.use = function(mw) {
	Dom.users.push(mw);
	return Dom;
};

function Handler(viewUrl, options) {
	this.initialViewUrl = viewUrl;
	this.options = options || {};
	if (!this.options.busyTimeout) this.options.busyTimeout = Dom.settings.busyTimeout;
	this.chainable = this.middleware.bind(this);
	this.chainable.author = this.author.bind(this);
	this.chainable.use = this.use.bind(this);
	this.authors = Dom.authors.slice(0);
	this.users = Dom.users.slice(0);
	this.pages = {};
	if (this.init) this.init(); // used by raja
}

Handler.prototype.middleware = function(req, res, next) {
	var h = this;
	var path = h.initialViewUrl;
	if (path !== undefined) {
		delete h.initialViewUrl;
		if (!/https?:/.test(path)) {
			var root = req.app.settings.statics;
			if (!root) return next(new Error("Cannot find view, undefined 'statics' application setting"));
			if (!path) path = "index";
			var path = Path.resolve(root, path);
			if (path.indexOf(root) !== 0) return next(new Error("Path outside statics dir\n" + path));
			if (path.slice(-1) == "/") path += "index";
			if (Path.extname(path) != ".html") path += ".html";
		}
		h.viewUrl = path;
	}
	var url = h.url = req.protocol + '://' + req.headers.host + req.url;
	if (url == h.viewUrl) {
		return next(new Error("The view has the same url as the requested page"));
	}
	h.instance(url, function(err, inst) {
		if (err) return next(err);
		h.build(inst, req, res, function(err) {
			if (err) return next(err);
		});
	});
};

Handler.prototype.build = function(inst, req, res, cb) {
	var h = this;
	var q = queue(1);
	if (!inst.html) {
		if (!inst.authorHtml) {
			if (!h.viewHtml) {
				q.defer(h.getView.bind(h), req);
			}
			q.defer(h.getAuthored.bind(h), inst, req, res);
		}
		q.defer(h.getUsed.bind(h), inst, req, res);
	}
	q.defer(h.finish.bind(h), inst, res)
	.defer(h.gc.bind(h))
	.awaitAll(cb);
};

Handler.prototype.instance = function(url, cb) {
	var h = this;
	var inst = h.pages[url];
	if (!inst) inst = h.pages[url] = {
		hits: 0,
		busyness: 0,
		url: url
	};
	inst.hits++;
	inst.atime = Date.now();
	inst.mtime = inst.atime;
	inst.lock = true;
	cb(null, inst);
};

Handler.prototype.finish = function(inst, res, cb) {
	res.type('text/html');
	res.set('Last-Modified', (new Date(inst.mtime)).toUTCString());
	res.send(inst.html);
	// call getUsed when requesting this url again
	delete inst.html;
	inst.lock = false;
	cb();
};

Handler.prototype.acquire = function(inst, cb) {
	if (inst.page) return cb();
	var busyTimeout = this.options.busyTimeout;
	this.gc(function() {
		Dom.pool.acquire(function(err, page) {
			if (err) return cb(err);
			inst.page = page;
			if (busyTimeout) page.on('busy', function(inst) {
				inst.busyness++;
				setTimeout(function() {
					inst.busyness--;
				}, busyTimeout);
			}.bind(null, inst));
			cb();
		});
	});
};

Handler.prototype.gc = function(cb) {
	if (Dom.pool.getPoolSize() < Dom.settings.max || Dom.pool.availableObjectsCount() > 0) return cb();
	var minScore = +Infinity;
	var minInst;
	var busyPages = 0;
	for (var url in this.pages) {
		var inst = this.pages[url];
		if (inst.lock || !inst.page) {
			continue;
		}
		if (inst.busyness > 0) {
			busyPages++;
		}
		var score = 0;
		if (inst.score !== undefined) {
			// allow a score to be set by application
			score = inst.score;
		} else {
			// or use our default scoring
			score = (inst.weight || 1) * 60000 * inst.hits / (Date.now() - inst.atime);
		}
		if (score < minScore) {
			minScore = score;
			minInst = inst;
		}
	}
	if (minInst) {
		this.release(minInst, cb);
	} else {
		if (busyPages == Dom.settings.max) {
			console.warn("Too many busy instances - lower busyTimeout of raise Dom.settings.max")
		}
		cb();
	}
};

Handler.prototype.getView = function(req, cb) {
	var h = this;
	if (h.viewHtml) return cb();
	var loader = /https?:/.test(h.viewUrl) ? h.loadRemote : h.loadLocal;
	loader.call(h, h.viewUrl, function(err, body) {
		if (!err || body) {
			h.viewHtml = body;
			h.mtime = Date.now();
		}	else {
			if (!err) err = new Error("Empty initial html in " + h.viewUrl);
			if (!err.code) err.code = 404;
		}
		cb(err);
	});
};

Handler.prototype.loadLocal = fs.readFile;
Handler.prototype.loadRemote = function(url, cb) {
	request(url, function(err, res, body) {
		cb(err, body);
	});
};

Handler.prototype.load = function(inst, req, cb) {
	var h = this;
	var opts = {};
	for (var key in h.options) {
		opts[key] = h.options[key];
	}
	if (!opts.content) opts.content = inst.authorHtml;
	if (!opts.cookie) opts.cookie = req.get('Cookie');
	if (opts.console === undefined) opts.console = true;
	if (opts.images === undefined) opts.images = false;
	if (opts.style === undefined && !Dom.settings.debug) opts.style = Dom.settings.style;
	this.acquire(inst, function(err) {
		if (err) return cb(err);
		inst.page.load(inst.url, opts, cb);
	});
};

Handler.prototype.getAuthored = function(inst, req, res, cb) {
	var h = this;
	if (inst.authorHtml) return cb();
	if (h.authors.length) {
		h.acquire(inst, function(err) {
			if (err) return cb(err);
			var obj = {
				content: h.viewHtml,
				console: true
			};
			if (!Dom.settings.debug) obj.style = Dom.settings.style;
			inst.page.preload(inst.url, obj);
			h.processMw(inst, h.authors, req, res);
			inst.page.wait('idle').html(function(err, html) {
				if (err) return cb(err);
				inst.authorHtml = html;
				inst.page.removeAllListeners();
				cb();
			});
		});
	} else {
		inst.authorHtml = h.viewHtml;
		cb();
	}
};

Handler.prototype.getUsed = function(inst, req, res, cb) {
	var h = this;
	if (inst.html) return cb();
	h.load(inst, req, function(err) {
		h.processMw(inst, h.users, req, res);
		inst.page.wait('idle').html(function(err, html) {
			if (err) return cb(err);
			inst.mtime = Date.now();
			inst.html = html;
			cb();
		});
	});
};

Handler.prototype.release = function(inst, cb) {
	var page = inst.page;
	if (!page) return cb();
	delete inst.page;
	page.removeAllListeners();
	page.unload(function(err) {
		Dom.pool.release(page);
		cb(err);
	});
};

Handler.prototype.processMw = function(inst, list, req, res) {
	if (!list || !list.length) return;
	for (var i=0, mw; i < list.length; i++) {
		mw = list[i];
		if (!mw) {
			console.error("Empty middleware");
			continue;
		}
		mw(inst, req, res);
	}
};

Handler.prototype.use = function(mw) {
	this.users.push(mw);
	return this.chainable;
};
Handler.prototype.author = function(mw) {
	this.authors.push(mw);
	return this.chainable;
};

function initPool(settings) {
	var opts = {};
	for (var prop in settings) opts[prop] = settings[prop];
	if (opts.debug) {
		if (opts.display != 0) {
			opts.display = 0;
			console.info("debug is on - using default display 0");
		}
		opts.max = 1;
	}
	if (!opts.name) opts.name = "webkitPool";
	if (!opts.create) opts.create = function(cb) {
		cb(null, WebKit(opts));
	};
	if (!opts.destroy) opts.destroy = function(client) {
		client.destroy();
		if (global.gc) {
			global.gc();
		}
	};
	if (!opts.max) opts.max = 1;
	var pool = Pool(opts);
	process.on('exit', function() {
		pool.drain(function() {
			pool.destroyAllNow();
		});
	});
	return pool;
}

<commit_msg>Do not change mtime when getting the instance<commit_after>var WebKit = require('webkitgtk');
var Pool = require('generic-pool').Pool;
var fs = require('fs');
var queue = require('queue-async');
var escapeStringRegexp = require('escape-string-regexp');
var request = require('request');
var Path = require('path');

var Dom = module.exports = function(model, options) {
	// init pool later, allowing user to set pool settings
	if (!Dom.pool) Dom.pool = initPool(Dom.settings);
	var h = new Handler(model, options);
	return h.chainable;
};

Dom.Handler = Handler;

Dom.settings = {
	min: 2,
	max: 16,
	busyTimeout: 0,
	idleTimeoutMillis: 1000000,
	refreshIdle: false,
	display: process.env.DISPLAY || 0,
	style: fs.readFileSync(__dirname + '/index.css'),
	debug: !!process.env.DEBUG
};
Dom.plugins = require('./plugins');

Dom.authors = [];
Dom.users = [Dom.plugins.nostylesheets];

Dom.author = function(mw) {
	Dom.authors.push(mw);
	return Dom;
};

Dom.use = function(mw) {
	Dom.users.push(mw);
	return Dom;
};

function Handler(viewUrl, options) {
	this.initialViewUrl = viewUrl;
	this.options = options || {};
	if (!this.options.busyTimeout) this.options.busyTimeout = Dom.settings.busyTimeout;
	this.chainable = this.middleware.bind(this);
	this.chainable.author = this.author.bind(this);
	this.chainable.use = this.use.bind(this);
	this.authors = Dom.authors.slice(0);
	this.users = Dom.users.slice(0);
	this.pages = {};
	if (this.init) this.init(); // used by raja
}

Handler.prototype.middleware = function(req, res, next) {
	var h = this;
	var path = h.initialViewUrl;
	if (path !== undefined) {
		delete h.initialViewUrl;
		if (!/https?:/.test(path)) {
			var root = req.app.settings.statics;
			if (!root) return next(new Error("Cannot find view, undefined 'statics' application setting"));
			if (!path) path = "index";
			var path = Path.resolve(root, path);
			if (path.indexOf(root) !== 0) return next(new Error("Path outside statics dir\n" + path));
			if (path.slice(-1) == "/") path += "index";
			if (Path.extname(path) != ".html") path += ".html";
		}
		h.viewUrl = path;
	}
	var url = h.url = req.protocol + '://' + req.headers.host + req.url;
	if (url == h.viewUrl) {
		return next(new Error("The view has the same url as the requested page"));
	}
	h.instance(url, function(err, inst) {
		if (err) return next(err);
		h.build(inst, req, res, function(err) {
			if (err) return next(err);
		});
	});
};

Handler.prototype.build = function(inst, req, res, cb) {
	var h = this;
	var q = queue(1);
	if (!inst.html) {
		if (!inst.authorHtml) {
			if (!h.viewHtml) {
				q.defer(h.getView.bind(h), req);
			}
			q.defer(h.getAuthored.bind(h), inst, req, res);
		}
		q.defer(h.getUsed.bind(h), inst, req, res);
	}
	q.defer(h.finish.bind(h), inst, res)
	.defer(h.gc.bind(h))
	.awaitAll(cb);
};

Handler.prototype.instance = function(url, cb) {
	var h = this;
	var inst = h.pages[url];
	if (!inst) inst = h.pages[url] = {
		hits: 0,
		busyness: 0,
		url: url,
		mtime: Date.now()
	};
	inst.hits++;
	inst.atime = Date.now();
	inst.lock = true;
	cb(null, inst);
};

Handler.prototype.finish = function(inst, res, cb) {
	res.type('text/html');
	res.set('Last-Modified', (new Date(inst.mtime)).toUTCString());
	res.send(inst.html);
	// call getUsed when requesting this url again
	delete inst.html;
	inst.lock = false;
	cb();
};

Handler.prototype.acquire = function(inst, cb) {
	if (inst.page) return cb();
	var busyTimeout = this.options.busyTimeout;
	this.gc(function() {
		Dom.pool.acquire(function(err, page) {
			if (err) return cb(err);
			inst.page = page;
			if (busyTimeout) page.on('busy', function(inst) {
				inst.busyness++;
				setTimeout(function() {
					inst.busyness--;
				}, busyTimeout);
			}.bind(null, inst));
			cb();
		});
	});
};

Handler.prototype.gc = function(cb) {
	if (Dom.pool.getPoolSize() < Dom.settings.max || Dom.pool.availableObjectsCount() > 0) return cb();
	var minScore = +Infinity;
	var minInst;
	var busyPages = 0;
	for (var url in this.pages) {
		var inst = this.pages[url];
		if (inst.lock || !inst.page) {
			continue;
		}
		if (inst.busyness > 0) {
			busyPages++;
		}
		var score = 0;
		if (inst.score !== undefined) {
			// allow a score to be set by application
			score = inst.score;
		} else {
			// or use our default scoring
			score = (inst.weight || 1) * 60000 * inst.hits / (Date.now() - inst.atime);
		}
		if (score < minScore) {
			minScore = score;
			minInst = inst;
		}
	}
	if (minInst) {
		this.release(minInst, cb);
	} else {
		if (busyPages == Dom.settings.max) {
			console.warn("Too many busy instances - lower busyTimeout of raise Dom.settings.max")
		}
		cb();
	}
};

Handler.prototype.getView = function(req, cb) {
	var h = this;
	if (h.viewHtml) return cb();
	var loader = /https?:/.test(h.viewUrl) ? h.loadRemote : h.loadLocal;
	loader.call(h, h.viewUrl, function(err, body) {
		if (!err || body) {
			h.viewHtml = body;
			h.mtime = Date.now();
		}	else {
			if (!err) err = new Error("Empty initial html in " + h.viewUrl);
			if (!err.code) err.code = 404;
		}
		cb(err);
	});
};

Handler.prototype.loadLocal = fs.readFile;
Handler.prototype.loadRemote = function(url, cb) {
	request(url, function(err, res, body) {
		cb(err, body);
	});
};

Handler.prototype.load = function(inst, req, cb) {
	var h = this;
	var opts = {};
	for (var key in h.options) {
		opts[key] = h.options[key];
	}
	if (!opts.content) opts.content = inst.authorHtml;
	if (!opts.cookie) opts.cookie = req.get('Cookie');
	if (opts.console === undefined) opts.console = true;
	if (opts.images === undefined) opts.images = false;
	if (opts.style === undefined && !Dom.settings.debug) opts.style = Dom.settings.style;
	this.acquire(inst, function(err) {
		if (err) return cb(err);
		inst.page.load(inst.url, opts, cb);
	});
};

Handler.prototype.getAuthored = function(inst, req, res, cb) {
	var h = this;
	if (inst.authorHtml) return cb();
	if (h.authors.length) {
		h.acquire(inst, function(err) {
			if (err) return cb(err);
			var obj = {
				content: h.viewHtml,
				console: true
			};
			if (!Dom.settings.debug) obj.style = Dom.settings.style;
			inst.page.preload(inst.url, obj);
			h.processMw(inst, h.authors, req, res);
			inst.page.wait('idle').html(function(err, html) {
				if (err) return cb(err);
				inst.authorHtml = html;
				inst.page.removeAllListeners();
				cb();
			});
		});
	} else {
		inst.authorHtml = h.viewHtml;
		cb();
	}
};

Handler.prototype.getUsed = function(inst, req, res, cb) {
	var h = this;
	if (inst.html) return cb();
	h.load(inst, req, function(err) {
		h.processMw(inst, h.users, req, res);
		inst.page.wait('idle').html(function(err, html) {
			if (err) return cb(err);
			inst.mtime = Date.now();
			inst.html = html;
			cb();
		});
	});
};

Handler.prototype.release = function(inst, cb) {
	var page = inst.page;
	if (!page) return cb();
	delete inst.page;
	page.removeAllListeners();
	page.unload(function(err) {
		Dom.pool.release(page);
		cb(err);
	});
};

Handler.prototype.processMw = function(inst, list, req, res) {
	if (!list || !list.length) return;
	for (var i=0, mw; i < list.length; i++) {
		mw = list[i];
		if (!mw) {
			console.error("Empty middleware");
			continue;
		}
		mw(inst, req, res);
	}
};

Handler.prototype.use = function(mw) {
	this.users.push(mw);
	return this.chainable;
};
Handler.prototype.author = function(mw) {
	this.authors.push(mw);
	return this.chainable;
};

function initPool(settings) {
	var opts = {};
	for (var prop in settings) opts[prop] = settings[prop];
	if (opts.debug) {
		if (opts.display != 0) {
			opts.display = 0;
			console.info("debug is on - using default display 0");
		}
		opts.max = 1;
	}
	if (!opts.name) opts.name = "webkitPool";
	if (!opts.create) opts.create = function(cb) {
		cb(null, WebKit(opts));
	};
	if (!opts.destroy) opts.destroy = function(client) {
		client.destroy();
		if (global.gc) {
			global.gc();
		}
	};
	if (!opts.max) opts.max = 1;
	var pool = Pool(opts);
	process.on('exit', function() {
		pool.drain(function() {
			pool.destroyAllNow();
		});
	});
	return pool;
}

<|endoftext|>


<commit_before>var express = require("express");
var app = express();
var path =require('path');
var bodyparser = require('body-parser');
app.use( bodyparser.json() );
app.use(bodyparser.urlencoded({extended: false}));

var request = require('./requestData');

var port = Number(process.argv[2]);
var apikey = process.argv[3];

var fakeMessage = {
  "text" : "##~~text, this field may accept markdown~~",
  // "attachments" : [
  //   {
  //     "title" : "Ask:",
  //     "text" : "Who am I?",
  //     "color" : "#666666",
  //     "images" : [
  //       {
  //         "url" : "https://lh4.googleusercontent.com/-hdQ2HH5-hog/AAAAAAAAAAI/AAAAAAAAAAA/GW2OyU89jXE/W96-H96/photo.jpg"
  //       }
  //     ]
  //   }
  // ]
};

// TODO: save log
app.post('/chengyu', function(req, res) {
  const { token, text, trigger_word, user_name } = req.body;
  if (token !== '8d37093bb3548d9be7d876147d288337') {
    res.status(404)        // HTTP status 404: NotFound
      .end('Not found');
  }
  var word = text.split(' ').slice(1).join('');
  console.log(word);
  request(apikey, word, function(data) {
    const {
      total, result, error_code, reason
    } = JSON.parse(data);
    console.log(total, error_code, reason);
    if (error_code !== 0) {
      fakeMessage.text = reason;
    } else {
      fakeMessage.text = result.reduce(function(string, v, i) {
        return string += v.name;
      }, '');
    }
    res.end(JSON.stringify(fakeMessage));
  });
})
app.listen(port);
<commit_msg>beauty res text<commit_after>var express = require("express");
var app = express();
var path =require('path');
var bodyparser = require('body-parser');
app.use( bodyparser.json() );
app.use(bodyparser.urlencoded({extended: false}));

var request = require('./requestData');

var port = Number(process.argv[2]);
var apikey = process.argv[3];

var fakeMessage = {
  "text" : "##~~text, this field may accept markdown~~",
  // "attachments" : [
  //   {
  //     "title" : "Ask:",
  //     "text" : "Who am I?",
  //     "color" : "#666666",
  //     "images" : [
  //       {
  //         "url" : "https://lh4.googleusercontent.com/-hdQ2HH5-hog/AAAAAAAAAAI/AAAAAAAAAAA/GW2OyU89jXE/W96-H96/photo.jpg"
  //       }
  //     ]
  //   }
  // ]
};

// TODO: save log
app.post('/chengyu', function(req, res) {
  const { token, text, trigger_word, user_name } = req.body;
  if (token !== '8d37093bb3548d9be7d876147d288337') {
    res.status(404)        // HTTP status 404: NotFound
      .end('Not found');
  }
  var word = text.split(' ').slice(1).join('');
  console.log(word);
  request(apikey, word, function(data) {
    const {
      total, result, error_code, reason
    } = JSON.parse(data);
    console.log(total, error_code, reason);
    if (error_code !== 0) {
      fakeMessage.text = reason;
    } else {
      fakeMessage.text = result.reduce(function(string, v, i) {
        return string += `${i+1}. ${v.name}\n`;
      }, '搜索结果：\n');
    }
    res.end(JSON.stringify(fakeMessage));
  });
})
app.listen(port);
<|endoftext|>


<commit_before>var express = require('express');

var app = express();

var http = require('http').Server(app);
var io = require('socket.io')(http);
var fs = require('fs');
var path = require('path');

var spawn = require('child_process').spawn;
var proc;

app.use('/', express.static(path.join(__dirname, 'stream')));

app.get('/', function(req, res) {
    res.sendFile(__dirname + '/index.html');
});

var sockets = {};

io.on('connection', function (socket) {
    sockets[socket.id] = socket;
    console.log("Total clients connected : ", Object.keys(sockets).length);

    socket.on('disconnect', function() {
        delete sockets[socket.id];

        // no more sockets, kill the stream
        if (Object.keys(sockets).length == 0) {
            app.set('watchingFile', false);
            if (proc) proc.kill();
            fs.unwatchFile('./stream/image_stream.jpg');
        }
    });

    socket.on('start-stream', function() {
        startStreaming(io);
    });
});

http.listen(3000, function() {
    console.log('listening on *:3000');
});

function stopStreaming() {
    if (Object.keys(sockets).length == 0) {
        app.set('watchingFile', false);
        if (proc) proc.kill();
        fs.unwatchFile('./stream/image_stream.jpg');
    }
}

function startStreaming(io) {
    if (app.get('watchingFile')) {
        io.sockets.emit('liveStream', 'image_stream.jpg?_t=' + (Math.random() * 100000));
        return;
    }

    var args = ["-w", "640", "-h", "480", "-o", "./stream/image_stream.jpg", "-t", "999999999", "-tl", "100"];
    proc = spawn('raspistill', args);

    console.log('Watching for changes...');

    app.set('watchingFile', true);

    fs.watchFile('./stream/image_stream.jpg', function(current, previous) {
        io.sockets.emit('liveStream', 'image_stream.jpg?_t=' + (Math.random() * 100000));
    });
}
<commit_msg>fix index.html path<commit_after>var express = require('express');

var app = express();

var http = require('http').Server(app);
var io = require('socket.io')(http);
var fs = require('fs');
var path = require('path');

var spawn = require('child_process').spawn;
var proc;

app.use('/', express.static(path.join(__dirname, 'stream')));

app.get('/', function(req, res) {
    res.sendFile(__dirname + '/public/index.html');
});

var sockets = {};

io.on('connection', function (socket) {
    sockets[socket.id] = socket;
    console.log("Total clients connected : ", Object.keys(sockets).length);

    socket.on('disconnect', function() {
        delete sockets[socket.id];

        // no more sockets, kill the stream
        if (Object.keys(sockets).length == 0) {
            app.set('watchingFile', false);
            if (proc) proc.kill();
            fs.unwatchFile('./stream/image_stream.jpg');
        }
    });

    socket.on('start-stream', function() {
        startStreaming(io);
    });
});

http.listen(3000, function() {
    console.log('listening on *:3000');
});

function stopStreaming() {
    if (Object.keys(sockets).length == 0) {
        app.set('watchingFile', false);
        if (proc) proc.kill();
        fs.unwatchFile('./stream/image_stream.jpg');
    }
}

function startStreaming(io) {
    if (app.get('watchingFile')) {
        io.sockets.emit('liveStream', 'image_stream.jpg?_t=' + (Math.random() * 100000));
        return;
    }

    var args = ["-w", "640", "-h", "480", "-o", "./stream/image_stream.jpg", "-t", "999999999", "-tl", "100"];
    proc = spawn('raspistill', args);

    console.log('Watching for changes...');

    app.set('watchingFile', true);

    fs.watchFile('./stream/image_stream.jpg', function(current, previous) {
        io.sockets.emit('liveStream', 'image_stream.jpg?_t=' + (Math.random() * 100000));
    });
}
<|endoftext|>


<commit_before>'use strict';

var gulp = require('gulp');
var glob = require('glob-all');
var path = require('path');
var _ = require('underscore');

var conf = {};

function findBase(a, b, l) {
  if (a === b) return a;

  l = l ? l - 1 : (a.length > b.length ? b.length : a.length);

  return findBase(a.substr(0, l), b.substr(0, l), l);
}

function createTask(task) {
  if (!task['dependencies'] && !task['task'])
    throw new Error('Tasks must either have dependencies or a task function!');

  gulp.task(task['name'], task['dependencies'], task['task']);
}

function loadTask(base) {
  return function (file) {
    var r = path.relative(base, file);
    var f = path.join(process.cwd(), file);
    var b = path.basename(r);
    var d = path.dirname(r);
    var e = path.extname(b);
    var x = require(f);
    var t = {};

    if (x['config'])
      if (_.isFunction(x['config']))
        t['config'] = x['config'](conf);
      else
        t['config'] = _.defaults(x['config'], conf);
    else
      t['config'] = _.defaults({}, conf);

    if (x['name'])
      if (_.isFunction(x['name']))
        t['name'] = x['name']();
      else
        t['name'] = _.defaults(x['config'], conf);
    else
      t['name'] = (d === '.' ? '' : d.replace(path.sep, ':') + ':') + path.basename(b, e);

    if (x['task'])
      t['task'] = x['task'];

    if (x['dependencies'])
      if (_.isArray(x['dependencies']))
        t['dependencies'] = x['dependencies'];
      else
        t['dependencies'] = [x['dependencies']];

    createTask(t);
  };
}

var Bootstrap = {

  /**
   * Merge `cfg` into the current configuration.
   *
   * @param {Object} cfg A configuration object
   * @returns {Bootstrap}
   */
  config: function (cfg) {
    conf = _.extend(conf, cfg);

    return this;
  },

  /**
   * Load tasks from paths.
   *
   * @param {string|string[]} paths A path or an array of paths
   * @returns {Bootstrap}
   */
  loadTasks: function (paths) {
    if (!Array.isArray(paths))
      paths = [paths];

    var files = glob.sync(paths).sort();
    var base = files.reduce(function (a, b) {
      return findBase(a, b);
    });

    if (files.length === 1) {
      base = path.dirname(base);
    }

    console.log('found base', base);

    files.forEach(loadTask(base));

    return this;
  }

};

module.exports = Bootstrap;
<commit_msg>Removed console.log<commit_after>'use strict';

var gulp = require('gulp');
var glob = require('glob-all');
var path = require('path');
var _ = require('underscore');

var conf = {};

function findBase(a, b, l) {
  if (a === b) return a;

  l = l ? l - 1 : (a.length > b.length ? b.length : a.length);

  return findBase(a.substr(0, l), b.substr(0, l), l);
}

function createTask(task) {
  if (!task['dependencies'] && !task['task'])
    throw new Error('Tasks must either have dependencies or a task function!');

  gulp.task(task['name'], task['dependencies'], task['task']);
}

function loadTask(base) {
  return function (file) {
    var r = path.relative(base, file);
    var f = path.join(process.cwd(), file);
    var b = path.basename(r);
    var d = path.dirname(r);
    var e = path.extname(b);
    var x = require(f);
    var t = {};

    if (x['config'])
      if (_.isFunction(x['config']))
        t['config'] = x['config'](conf);
      else
        t['config'] = _.defaults(x['config'], conf);
    else
      t['config'] = _.defaults({}, conf);

    if (x['name'])
      if (_.isFunction(x['name']))
        t['name'] = x['name']();
      else
        t['name'] = _.defaults(x['config'], conf);
    else
      t['name'] = (d === '.' ? '' : d.replace(path.sep, ':') + ':') + path.basename(b, e);

    if (x['task'])
      t['task'] = x['task'];

    if (x['dependencies'])
      if (_.isArray(x['dependencies']))
        t['dependencies'] = x['dependencies'];
      else
        t['dependencies'] = [x['dependencies']];

    createTask(t);
  };
}

var Bootstrap = {

  /**
   * Merge `cfg` into the current configuration.
   *
   * @param {Object} cfg A configuration object
   * @returns {Bootstrap}
   */
  config: function (cfg) {
    conf = _.extend(conf, cfg);

    return this;
  },

  /**
   * Load tasks from paths.
   *
   * @param {string|string[]} paths A path or an array of paths
   * @returns {Bootstrap}
   */
  loadTasks: function (paths) {
    if (!Array.isArray(paths))
      paths = [paths];

    var files = glob.sync(paths).sort();
    var base = files.reduce(function (a, b) {
      return findBase(a, b);
    });

    if (files.length === 1) {
      base = path.dirname(base);
    }

    files.forEach(loadTask(base));

    return this;
  }

};

module.exports = Bootstrap;
<|endoftext|>


<commit_before>//Does some basic defintions in order to make the bot function
const Discord = require('discord.js');
const client = new Discord.Client();

//Sends startup message when fired
client.on('ready',() => {
  console.log('Frogbot ready for combat!');
  //Sets the bot's game display message
  client.user.setPresence({ status: 'online', game: { name: 'on a unicycle', type: 0 } });
});

//Sets the prefix required to activate the bot
var prefix = '~'

//Fires on message send in any channel
client.on('message', message => {
    //Sets the bot's game display message

    //Ignores message if it does not start with prefix
    if (!message.content.startsWith(prefix)) return;

    //Ignores message if message is sent by another bot or itself
    if (message.author.bot) return;

    //Displays the amount of time it took in miliseconds to receive command and to respond
    if (message.content.toLowerCase() === (prefix + 'ping')) {
      //Creates a variable to save the user's message time
      var startTime = message.createdTimestamp;

      //Sends a placeholder message to compare times
      message.channel.send(`:ping_pong: Pong!`).then(message => {
          //Subtracts this message's time by the user's message to calculate ping
          message.edit(`This message took \`${Math.round(message.createdTimestamp - startTime)} ms\` to reach you!`);
          //Checks to see if there is a 0 or negative ping value, then displays error message
          if (Math.round(message.createdTimestamp - startTime <= 0)) {
            message.channel.send('Wait a minute, that can\'t be right...')
          }
      });
    } else

    //Message that tells commands, aka huge mess. Need to find a better way to display in source code.
    if (message.content.toLowerCase() === (prefix + 'commands')) {
      message.channel.send(`\`\`\`css\nPing - Displays the amount of time it takes the bot to recieve your message and send a response\nDatBoi - Here comes dat boi\nOShitWaddup - O shit waddup\n\`\`\``);
    } else

    //Responds with a meme to a meme hypothesis
    if (message.content.toLowerCase() === (prefix + 'datboi')) {
      message.channel.send('O shit waddup!');
    } else

    //Responds with a meme to a meme conclusion
    if (message.content.toLowerCase() === (prefix + 'oshitwaddup')) {
      message.channel.send('Here comes dat boi!');
    } else

    //Responds with Pingu noises
    if (message.content.toLowerCase() === (prefix + 'pingu')) {
      message.channel.send('Noot Noot :penguin:');
    } else

    //Responds to a message with a relevant image
    if (message.content.toLowerCase() === (prefix + 'bitchholdon')) {
      message.channel.send("", {
        file: "https://i.imgur.com/XHBa71T.jpg"
      });
    } else

    //B emoji version of ping command
    if (message.content.toLowerCase() === (prefix + '🅱ing')) {
      //Creates a variable to save the user's message time
      var startTime = message.createdTimestamp;

      //Sends a placeholder message to compare times
      message.channel.send(`:ping_pong: :b:ong!`).then(message => {
          //Subtracts this message's time by the user's message to calculate ping
          message.edit(`This :b:essage took \`${Math.round(message.createdTimestamp - startTime)} ms\` to :b:each you!`);
          //Checks to see if there is a 0 or negative ping value, then displays error message
          if (Math.round(message.createdTimestamp - startTime <= 0)) {
            message.channel.send('Wait a :b:inute, that can\'t be :b:ight...')
          }
      });
    } else

    //Testing rolling dice command
    //if (message.content.test(/^~r\s\dd\d+/)) {
      //message.channel.send("It worked!");
      //var messageArray = message.content.split(/[\sd]+/)
    //} else

    //Debug message
    message.channel.send("It didn't work boss!");
});

//Tells the bot what token to login with
client.login(process.env.BotToken);

//Web application portion that ensures Heroku never falls asleep
const express = require('express');
const app = express();

// set the port of our application
// process.env.PORT lets the port be set by Heroku
const port = process.env.PORT || 5000;

// set the view engine to ejs
app.set('view engine', 'ejs');

// make express look in the `public` directory for assets (css/js/img)
app.use(express.static(__dirname + '/public'));

// set the home page route
app.get('/', (request, response) => {
    // ejs render automatically looks in the views folder
    response.render('index');
});

app.listen(port, () => {
    // will echo 'Our app is running on http://localhost:5000 when run locally'
    console.log('Our app is running on http://localhost:' + port);
});

// pings server every 15 minutes to prevent dynos from sleeping
setInterval(() => {
 http.get('http://your-app-name.herokuapp.com');
}, 900000);
<commit_msg>Fixing B emoji command<commit_after>//Does some basic defintions in order to make the bot function
const Discord = require('discord.js');
const client = new Discord.Client();

//Sends startup message when fired
client.on('ready',() => {
  console.log('Frogbot ready for combat!');
  //Sets the bot's game display message
  client.user.setPresence({ status: 'online', game: { name: 'on a unicycle', type: 0 } });
});

//Sets the prefix required to activate the bot
var prefix = '~'

//Fires on message send in any channel
client.on('message', message => {
    //Sets the bot's game display message

    //Ignores message if it does not start with prefix
    if (!message.content.startsWith(prefix)) return;

    //Ignores message if message is sent by another bot or itself
    if (message.author.bot) return;

    //Displays the amount of time it took in miliseconds to receive command and to respond
    if (message.content.toLowerCase() === (prefix + 'ping')) {
      //Creates a variable to save the user's message time
      var startTime = message.createdTimestamp;

      //Sends a placeholder message to compare times
      message.channel.send(`:ping_pong: Pong!`).then(message => {
          //Subtracts this message's time by the user's message to calculate ping
          message.edit(`This message took \`${Math.round(message.createdTimestamp - startTime)} ms\` to reach you!`);
          //Checks to see if there is a 0 or negative ping value, then displays error message
          if (Math.round(message.createdTimestamp - startTime <= 0)) {
            message.channel.send('Wait a minute, that can\'t be right...')
          }
      });
    } else

    //Message that tells commands, aka huge mess. Need to find a better way to display in source code.
    if (message.content.toLowerCase() === (prefix + 'commands')) {
      message.channel.send(`\`\`\`css\nPing - Displays the amount of time it takes the bot to recieve your message and send a response\nDatBoi - Here comes dat boi\nOShitWaddup - O shit waddup\n\`\`\``);
    } else

    //Responds with a meme to a meme hypothesis
    if (message.content.toLowerCase() === (prefix + 'datboi')) {
      message.channel.send('O shit waddup!');
    } else

    //Responds with a meme to a meme conclusion
    if (message.content.toLowerCase() === (prefix + 'oshitwaddup')) {
      message.channel.send('Here comes dat boi!');
    } else

    //Responds with Pingu noises
    if (message.content.toLowerCase() === (prefix + 'pingu')) {
      message.channel.send('Noot Noot :penguin:');
    } else

    //Responds to a message with a relevant image
    if (message.content.toLowerCase() === (prefix + 'bitchholdon')) {
      message.channel.send("", {
        file: "https://i.imgur.com/XHBa71T.jpg"
      });
    } else

    //B emoji version of ping command
    if (message.content.toLowerCase() == (prefix + ':b:ing') || message.content.toLowerCase() == (prefix + '🅱ing')) {
      //Creates a variable to save the user's message time
      var startTime = message.createdTimestamp;

      //Sends a placeholder message to compare times
      message.channel.send(`:ping_pong: :b:ong!`).then(message => {
          //Subtracts this message's time by the user's message to calculate ping
          message.edit(`This :b:essage took \`${Math.round(message.createdTimestamp - startTime)} ms\` to :b:each you!`);
          //Checks to see if there is a 0 or negative ping value, then displays error message
          if (Math.round(message.createdTimestamp - startTime <= 0)) {
            message.channel.send('Wait a :b:inute, that can\'t be :b:ight...')
          }
      });
    } else

    //Testing rolling dice command
    //if (message.content.test(/^~r\s\dd\d+/)) {
      //message.channel.send("It worked!");
      //var messageArray = message.content.split(/[\sd]+/)
    //} else

    //Debug message
    message.channel.send("It didn't work boss!");
});

//Tells the bot what token to login with
client.login(process.env.BotToken);

//Web application portion that ensures Heroku never falls asleep
const express = require('express');
const app = express();

// set the port of our application
// process.env.PORT lets the port be set by Heroku
const port = process.env.PORT || 5000;

// set the view engine to ejs
app.set('view engine', 'ejs');

// make express look in the `public` directory for assets (css/js/img)
app.use(express.static(__dirname + '/public'));

// set the home page route
app.get('/', (request, response) => {
    // ejs render automatically looks in the views folder
    response.render('index');
});

app.listen(port, () => {
    // will echo 'Our app is running on http://localhost:5000 when run locally'
    console.log('Our app is running on http://localhost:' + port);
});

// pings server every 15 minutes to prevent dynos from sleeping
setInterval(() => {
 http.get('http://your-app-name.herokuapp.com');
}, 900000);
<|endoftext|>


<commit_before>'use strict';
var assert = require('assert');
var minimatch = require('minimatch');
var glob = require('glob');
var path = require('path');
var slash = require('slash');

var defaultOptions = {
  publicPath: true,
  hash: false,
  jsExtensions: ['.js'],
  cssExtensions: ['.css']
};

function isObject (v) {
  return v !== null && v !== undefined && typeof v === 'object' && !isArray(v);
}

function isBoolean (v) {
  return v === true || v === false;
}

function isString (v) {
  return v !== null && v !== undefined && (typeof v === 'string' || v instanceof String);
}

function isArray (v) {
  return Array.isArray(v);
}

function hasExtension (v, ending) {
  if (v.indexOf('?') !== -1) { // Remove anything after `?`
    v = v.substr(0, v.indexOf('?'));
  }
  var lastIndex = v.lastIndexOf(ending);
  return lastIndex !== -1 && lastIndex === v.length - ending.length;
}

function hasExtensions (v, extensions) {
  var found = false;
  var i;
  var count = extensions.length;
  for (i = 0; i < count; i++) {
    found = hasExtension(v, extensions[i]);
    if (found) {
      break;
    }
  }
  return found;
}

function isOneOf (v, values) {
  return values.indexOf(v) !== -1;
}

function forOwn (object, iterator) {
  var properties = Object.keys(object);
  var propertyCount = properties.length;
  var property;

  for (var i = 0; i < propertyCount; i++) {
    property = properties[i];
    iterator(object[property], property, object);
  }
}

function extend (target, source) {
  forOwn(source, function (value, property) {
    target[property] = value;
  });

  return target;
}

function HtmlWebpackIncludeAssetsPlugin (options) {
  assert(isObject(options), 'HtmlWebpackIncludeAssetsPlugin options are required');
  var assets;
  if (isString(options.assets) || isObject(options.assets)) {
    assets = [options.assets];
  } else {
    assets = options.assets;
  }
  assert(isArray(assets), 'HtmlWebpackIncludeAssetsPlugin options must have an assets key with an array or string value');
  var jsExtensions;
  if (options.jsExtensions !== undefined) {
    if (isString(options.jsExtensions)) {
      jsExtensions = [options.jsExtensions];
    } else {
      jsExtensions = options.jsExtensions;
      assert(isArray(jsExtensions), 'HtmlWebpackIncludeAssetsPlugin options jsExtensions key should be a string or array of strings (' + jsExtensions + ')');
      var jsExtensionCount = jsExtensions.length;
      var jsExtension;
      for (var j = 0; j < jsExtensionCount; j++) {
        jsExtension = jsExtensions[j];
        assert(isString(jsExtension), 'HtmlWebpackIncludeAssetsPlugin options jsExtensions key array should not contain non-strings (' + jsExtension + ')');
      }
    }
  } else {
    jsExtensions = defaultOptions.jsExtensions;
  }
  var cssExtensions;
  if (options.cssExtensions !== undefined) {
    if (isString(options.cssExtensions)) {
      cssExtensions = [options.cssExtensions];
    } else {
      cssExtensions = options.cssExtensions;
      assert(isArray(cssExtensions), 'HtmlWebpackIncludeAssetsPlugin options cssExtensions key should be a string or array of strings (' + cssExtensions + ')');
      var cssExtensionCount = cssExtensions.length;
      var cssExtension;
      for (var c = 0; c < cssExtensionCount; c++) {
        cssExtension = cssExtensions[c];
        assert(isString(cssExtension), 'HtmlWebpackIncludeAssetsPlugin options cssExtensions key array should not contain non-strings (' + cssExtension + ')');
      }
    }
  } else {
    cssExtensions = defaultOptions.cssExtensions;
  }
  var assetCount = assets.length;
  var asset;
  for (var i = 0; i < assetCount; i++) {
    asset = assets[i];
    if (isString(asset)) {
      assert(hasExtensions(asset, jsExtensions) || hasExtensions(asset, cssExtensions),
        'HtmlWebpackIncludeAssetsPlugin options assets key array should only contain strings ending with the js or css extensions (' + asset + ')');
    } else if (isObject(asset)) {
      assert(isString(asset.path),
        'HtmlWebpackIncludeAssetsPlugin options assets key array objects path property must be a string (' + asset.path + ')');
      if (asset.glob !== undefined) {
        assert(isString(asset.glob),
          'HtmlWebpackIncludeAssetsPlugin options assets key array objects glob property should be a string (' + asset.glob + ')');
      }
      if (asset.globPath !== undefined) {
        assert(isString(asset.globPath),
          'HtmlWebpackIncludeAssetsPlugin options assets key array objects globPath property should be a string (' + asset.globPath + ')');
      }
      if (asset.type !== undefined) {
        assert(isOneOf(asset.type, ['js', 'css']),
          'HtmlWebpackIncludeAssetsPlugin options assets key array objects type property should be a string set to either `js` or `css` (' + asset.type + ')');
      } else {
        if (asset.glob !== undefined) {
          assert(hasExtensions(asset.glob, jsExtensions) || hasExtensions(asset.glob, cssExtensions),
            'HtmlWebpackIncludeAssetsPlugin options assets key array objects glob property should only contain strings ending with the js or css extensions if the type property is not set (' + asset + ')');
        } else {
          assert(hasExtensions(asset.path, jsExtensions) || hasExtensions(asset.path, cssExtensions),
            'HtmlWebpackIncludeAssetsPlugin options assets key array objects path property should only contain strings ending with the js or css extensions if the type property is not set (' + asset + ')');
        }
      }
      if (asset.attributes !== undefined) {
        assert(isObject(asset.attributes), 'HtmlWebpackIncludeAssetsPlugin options assets key array objects attributes property should be an object');
        forOwn(asset.attributes, function (value) {
          assert(isString(value) || isBoolean(value), 'HtmlWebpackIncludeAssetsPlugin options assets key array objects attributes property should be an object with string or boolean values');
        });
      }
    } else {
      assert(false, 'HtmlWebpackIncludeAssetsPlugin options assets key array must contain only strings and objects (' + asset + ')');
    }
  }
  assert(isBoolean(options.append), 'HtmlWebpackIncludeAssetsPlugin options must have an append key with a boolean value');
  var publicPath;
  if (options.publicPath !== undefined) {
    assert(isBoolean(options.publicPath) || isString(options.publicPath),
      'HtmlWebpackIncludeAssetsPlugin options should specify a publicPath that is either a boolean or a string');
    publicPath = options.publicPath;
  } else {
    publicPath = defaultOptions.publicPath;
  }
  var hash;
  if (options.hash !== undefined) {
    assert(isBoolean(options.hash), 'HtmlWebpackIncludeAssetsPlugin options should specify a hash key with a boolean value');
    hash = options.hash;
  } else {
    hash = defaultOptions.hash;
  }
  var files;
  if (isString(options.files)) {
    files = [options.files];
  } else {
    files = options.files;
  }
  if (files !== undefined) {
    assert(isArray(files), 'HtmlWebpackIncludeAssetsPlugin options should specify a files key with an array or string value');
    var fileCount = files.length;
    var file;
    for (var f = 0; f < fileCount; f++) {
      file = files[f];
      assert(isString(file),
        'HtmlWebpackIncludeAssetsPlugin options files key array must contain only strings (' + file + ')');
    }
  }
  this.options = {
    assets: assets,
    jsExtensions: jsExtensions,
    cssExtensions: cssExtensions,
    append: options.append,
    publicPath: publicPath,
    hash: hash,
    files: files
  };
}

HtmlWebpackIncludeAssetsPlugin.prototype.apply = function (compiler) {
  var self = this;

  // Hook into the html-webpack-plugin processing
  function onCompilation (compilation) {
    var shouldSkip = function (htmlPluginData) {
      var files = self.options.files;
      return files !== undefined && !files.some(function (file) {
        return minimatch(htmlPluginData.outputName, file);
      });
    };

    var defaultPublicPath;
    var getAssetPath = function (includeAssetPath) {
      var publicPath = self.options.publicPath;
      var hash = self.options.hash;
      var includeAssetPrefix = publicPath === true ? defaultPublicPath : isString(publicPath) ? publicPath : '';
      var includeAssetHash = hash === true ? ('?' + compilation.hash) : '';
      return includeAssetPrefix + includeAssetPath + includeAssetHash;
    };

    function onBeforeHtmlGeneration (htmlPluginData, callback) {
      if (shouldSkip(htmlPluginData)) {
        if (callback) {
          return callback(null, htmlPluginData);
        } else {
          return Promise.resolve(htmlPluginData);
        }
      }

      var includeAssets = self.options.assets;
      var jsExtensions = self.options.jsExtensions;
      var cssExtensions = self.options.cssExtensions;
      var appendAssets = self.options.append;
      var assets = htmlPluginData.assets;
      defaultPublicPath = assets.publicPath;

      var includeAsset;
      var includeAssetString;
      var includeAssetPaths;
      var includeAssetCount;
      var includeAssetPath;
      var includeAssetType;
      var includeCount = includeAssets.length;
      var jsAssets = [];
      var cssAssets = [];
      for (var i = 0; i < includeCount; i++) {
        includeAsset = includeAssets[i];
        if (isObject(includeAsset)) {
          includeAssetType = includeAsset.type;
          if (includeAsset.glob === undefined) {
            includeAssetPaths = [includeAsset.path];
          } else {
            var cwd = includeAsset.globPath !== undefined ? includeAsset.globPath : path.join(compiler.options.output.path, includeAsset.path);

            var globOptions = {cwd: cwd};

                    // assets will be an array of strings with all matching asset file names
            includeAssetPaths = glob.sync(includeAsset.glob, globOptions).map(
                        function (globAsset) {
                          return slash(path.join(includeAsset.path, globAsset));
                        });
          }
        } else {
          includeAssetType = null;
          includeAssetPaths = [includeAsset];
        }
        includeAssetCount = includeAssetPaths.length;
        for (var a = 0; a < includeAssetCount; a++) {
          includeAssetPath = includeAssetPaths[a];
          includeAssetString = getAssetPath(includeAssetPath);
          if ((includeAssetType && includeAssetType === 'js') || hasExtensions(includeAssetString, jsExtensions)) {
            if (assets.js.indexOf(includeAssetString) === -1 && jsAssets.indexOf(includeAssetString) === -1) {
              jsAssets.push(includeAssetString);
            }
          } else if ((includeAssetType && includeAssetType === 'css') || hasExtensions(includeAssetString, cssExtensions)) {
            if (assets.css.indexOf(includeAssetString) === -1 && cssAssets.indexOf(includeAssetString) === -1) {
              cssAssets.push(includeAssetString);
            }
          }
        }
      }
      if (appendAssets) {
        assets.js = assets.js.concat(jsAssets);
        assets.css = assets.css.concat(cssAssets);
      } else {
        assets.js = jsAssets.concat(assets.js);
        assets.css = cssAssets.concat(assets.css);
      }

      if (callback) {
        callback(null, htmlPluginData);
      } else {
        return Promise.resolve(htmlPluginData);
      }
    }

    function onAlterAssetTag (htmlPluginData, callback) {
      var tags;
      var tagCount;
      var tag;
      var includeAssets = self.options.assets;
      var assetAttributes;
      var findAttributesForAsset = function (assets, href) {
        var assetCount = assets.length;
        var asset;

        for (var i = 0; i < assetCount; i++) {
          asset = assets[i];
          if (!isString(asset) && asset.attributes && href === getAssetPath(asset.path)) {
            return asset.attributes;
          }
        }
        return null;
      };

      if (shouldSkip(htmlPluginData)) {
        if (callback) {
          return callback(null, htmlPluginData);
        } else {
          return Promise.resolve(htmlPluginData);
        }
      }

      tags = htmlPluginData.head.concat(htmlPluginData.body);
      tagCount = tags.length;
      for (var i = 0; i < tagCount; i++) {
        tag = tags[i];
        assetAttributes = findAttributesForAsset(includeAssets, tag.attributes.href || tag.attributes.src);
        if (assetAttributes) {
          extend(tag.attributes, assetAttributes);
        }
      }

      if (callback) {
        callback(null, htmlPluginData);
      } else {
        return Promise.resolve(htmlPluginData);
      }
    }

    // Webpack 4+
    if (compilation.hooks) {
      compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration.tapAsync('htmlWebpackIncludeAssetsPlugin', onBeforeHtmlGeneration);
      compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('htmlWebpackIncludeAssetsPlugin', onAlterAssetTag);
    } else {
        // Webpack 3
      compilation.plugin('html-webpack-plugin-before-html-generation', onBeforeHtmlGeneration);
      compilation.plugin('html-webpack-plugin-alter-asset-tags', onAlterAssetTag);
    }
  }

  // Webpack 4+
  if (compiler.hooks) {
    compiler.hooks.compilation.tap('htmlWebpackIncludeAssetsPlugin', onCompilation);
  } else {
    // Webpack 3
    compiler.plugin('compilation', onCompilation);
  }
};

module.exports = HtmlWebpackIncludeAssetsPlugin;
<commit_msg>fix indentation<commit_after>'use strict';
var assert = require('assert');
var minimatch = require('minimatch');
var glob = require('glob');
var path = require('path');
var slash = require('slash');

var defaultOptions = {
  publicPath: true,
  hash: false,
  jsExtensions: ['.js'],
  cssExtensions: ['.css']
};

function isObject (v) {
  return v !== null && v !== undefined && typeof v === 'object' && !isArray(v);
}

function isBoolean (v) {
  return v === true || v === false;
}

function isString (v) {
  return v !== null && v !== undefined && (typeof v === 'string' || v instanceof String);
}

function isArray (v) {
  return Array.isArray(v);
}

function hasExtension (v, ending) {
  if (v.indexOf('?') !== -1) { // Remove anything after `?`
    v = v.substr(0, v.indexOf('?'));
  }
  var lastIndex = v.lastIndexOf(ending);
  return lastIndex !== -1 && lastIndex === v.length - ending.length;
}

function hasExtensions (v, extensions) {
  var found = false;
  var i;
  var count = extensions.length;
  for (i = 0; i < count; i++) {
    found = hasExtension(v, extensions[i]);
    if (found) {
      break;
    }
  }
  return found;
}

function isOneOf (v, values) {
  return values.indexOf(v) !== -1;
}

function forOwn (object, iterator) {
  var properties = Object.keys(object);
  var propertyCount = properties.length;
  var property;

  for (var i = 0; i < propertyCount; i++) {
    property = properties[i];
    iterator(object[property], property, object);
  }
}

function extend (target, source) {
  forOwn(source, function (value, property) {
    target[property] = value;
  });

  return target;
}

function HtmlWebpackIncludeAssetsPlugin (options) {
  assert(isObject(options), 'HtmlWebpackIncludeAssetsPlugin options are required');
  var assets;
  if (isString(options.assets) || isObject(options.assets)) {
    assets = [options.assets];
  } else {
    assets = options.assets;
  }
  assert(isArray(assets), 'HtmlWebpackIncludeAssetsPlugin options must have an assets key with an array or string value');
  var jsExtensions;
  if (options.jsExtensions !== undefined) {
    if (isString(options.jsExtensions)) {
      jsExtensions = [options.jsExtensions];
    } else {
      jsExtensions = options.jsExtensions;
      assert(isArray(jsExtensions), 'HtmlWebpackIncludeAssetsPlugin options jsExtensions key should be a string or array of strings (' + jsExtensions + ')');
      var jsExtensionCount = jsExtensions.length;
      var jsExtension;
      for (var j = 0; j < jsExtensionCount; j++) {
        jsExtension = jsExtensions[j];
        assert(isString(jsExtension), 'HtmlWebpackIncludeAssetsPlugin options jsExtensions key array should not contain non-strings (' + jsExtension + ')');
      }
    }
  } else {
    jsExtensions = defaultOptions.jsExtensions;
  }
  var cssExtensions;
  if (options.cssExtensions !== undefined) {
    if (isString(options.cssExtensions)) {
      cssExtensions = [options.cssExtensions];
    } else {
      cssExtensions = options.cssExtensions;
      assert(isArray(cssExtensions), 'HtmlWebpackIncludeAssetsPlugin options cssExtensions key should be a string or array of strings (' + cssExtensions + ')');
      var cssExtensionCount = cssExtensions.length;
      var cssExtension;
      for (var c = 0; c < cssExtensionCount; c++) {
        cssExtension = cssExtensions[c];
        assert(isString(cssExtension), 'HtmlWebpackIncludeAssetsPlugin options cssExtensions key array should not contain non-strings (' + cssExtension + ')');
      }
    }
  } else {
    cssExtensions = defaultOptions.cssExtensions;
  }
  var assetCount = assets.length;
  var asset;
  for (var i = 0; i < assetCount; i++) {
    asset = assets[i];
    if (isString(asset)) {
      assert(hasExtensions(asset, jsExtensions) || hasExtensions(asset, cssExtensions),
        'HtmlWebpackIncludeAssetsPlugin options assets key array should only contain strings ending with the js or css extensions (' + asset + ')');
    } else if (isObject(asset)) {
      assert(isString(asset.path),
        'HtmlWebpackIncludeAssetsPlugin options assets key array objects path property must be a string (' + asset.path + ')');
      if (asset.glob !== undefined) {
        assert(isString(asset.glob),
          'HtmlWebpackIncludeAssetsPlugin options assets key array objects glob property should be a string (' + asset.glob + ')');
      }
      if (asset.globPath !== undefined) {
        assert(isString(asset.globPath),
          'HtmlWebpackIncludeAssetsPlugin options assets key array objects globPath property should be a string (' + asset.globPath + ')');
      }
      if (asset.type !== undefined) {
        assert(isOneOf(asset.type, ['js', 'css']),
          'HtmlWebpackIncludeAssetsPlugin options assets key array objects type property should be a string set to either `js` or `css` (' + asset.type + ')');
      } else {
        if (asset.glob !== undefined) {
          assert(hasExtensions(asset.glob, jsExtensions) || hasExtensions(asset.glob, cssExtensions),
            'HtmlWebpackIncludeAssetsPlugin options assets key array objects glob property should only contain strings ending with the js or css extensions if the type property is not set (' + asset + ')');
        } else {
          assert(hasExtensions(asset.path, jsExtensions) || hasExtensions(asset.path, cssExtensions),
            'HtmlWebpackIncludeAssetsPlugin options assets key array objects path property should only contain strings ending with the js or css extensions if the type property is not set (' + asset + ')');
        }
      }
      if (asset.attributes !== undefined) {
        assert(isObject(asset.attributes), 'HtmlWebpackIncludeAssetsPlugin options assets key array objects attributes property should be an object');
        forOwn(asset.attributes, function (value) {
          assert(isString(value) || isBoolean(value), 'HtmlWebpackIncludeAssetsPlugin options assets key array objects attributes property should be an object with string or boolean values');
        });
      }
    } else {
      assert(false, 'HtmlWebpackIncludeAssetsPlugin options assets key array must contain only strings and objects (' + asset + ')');
    }
  }
  assert(isBoolean(options.append), 'HtmlWebpackIncludeAssetsPlugin options must have an append key with a boolean value');
  var publicPath;
  if (options.publicPath !== undefined) {
    assert(isBoolean(options.publicPath) || isString(options.publicPath),
      'HtmlWebpackIncludeAssetsPlugin options should specify a publicPath that is either a boolean or a string');
    publicPath = options.publicPath;
  } else {
    publicPath = defaultOptions.publicPath;
  }
  var hash;
  if (options.hash !== undefined) {
    assert(isBoolean(options.hash), 'HtmlWebpackIncludeAssetsPlugin options should specify a hash key with a boolean value');
    hash = options.hash;
  } else {
    hash = defaultOptions.hash;
  }
  var files;
  if (isString(options.files)) {
    files = [options.files];
  } else {
    files = options.files;
  }
  if (files !== undefined) {
    assert(isArray(files), 'HtmlWebpackIncludeAssetsPlugin options should specify a files key with an array or string value');
    var fileCount = files.length;
    var file;
    for (var f = 0; f < fileCount; f++) {
      file = files[f];
      assert(isString(file),
        'HtmlWebpackIncludeAssetsPlugin options files key array must contain only strings (' + file + ')');
    }
  }
  this.options = {
    assets: assets,
    jsExtensions: jsExtensions,
    cssExtensions: cssExtensions,
    append: options.append,
    publicPath: publicPath,
    hash: hash,
    files: files
  };
}

HtmlWebpackIncludeAssetsPlugin.prototype.apply = function (compiler) {
  var self = this;

  // Hook into the html-webpack-plugin processing
  function onCompilation (compilation) {
    var shouldSkip = function (htmlPluginData) {
      var files = self.options.files;
      return files !== undefined && !files.some(function (file) {
        return minimatch(htmlPluginData.outputName, file);
      });
    };

    var defaultPublicPath;
    var getAssetPath = function (includeAssetPath) {
      var publicPath = self.options.publicPath;
      var hash = self.options.hash;
      var includeAssetPrefix = publicPath === true ? defaultPublicPath : isString(publicPath) ? publicPath : '';
      var includeAssetHash = hash === true ? ('?' + compilation.hash) : '';
      return includeAssetPrefix + includeAssetPath + includeAssetHash;
    };

    function onBeforeHtmlGeneration (htmlPluginData, callback) {
      if (shouldSkip(htmlPluginData)) {
        if (callback) {
          return callback(null, htmlPluginData);
        } else {
          return Promise.resolve(htmlPluginData);
        }
      }

      var includeAssets = self.options.assets;
      var jsExtensions = self.options.jsExtensions;
      var cssExtensions = self.options.cssExtensions;
      var appendAssets = self.options.append;
      var assets = htmlPluginData.assets;
      defaultPublicPath = assets.publicPath;

      var includeAsset;
      var includeAssetString;
      var includeAssetPaths;
      var includeAssetCount;
      var includeAssetPath;
      var includeAssetType;
      var includeCount = includeAssets.length;
      var jsAssets = [];
      var cssAssets = [];
      for (var i = 0; i < includeCount; i++) {
        includeAsset = includeAssets[i];
        if (isObject(includeAsset)) {
          includeAssetType = includeAsset.type;
          if (includeAsset.glob === undefined) {
            includeAssetPaths = [includeAsset.path];
          } else {
            var cwd = includeAsset.globPath !== undefined ? includeAsset.globPath : path.join(compiler.options.output.path, includeAsset.path);

            var globOptions = {cwd: cwd};

            // assets will be an array of strings with all matching asset file names
            includeAssetPaths = glob.sync(includeAsset.glob, globOptions).map(
              function (globAsset) {
                return slash(path.join(includeAsset.path, globAsset));
              }
            );
          }
        } else {
          includeAssetType = null;
          includeAssetPaths = [includeAsset];
        }
        includeAssetCount = includeAssetPaths.length;
        for (var a = 0; a < includeAssetCount; a++) {
          includeAssetPath = includeAssetPaths[a];
          includeAssetString = getAssetPath(includeAssetPath);
          if ((includeAssetType && includeAssetType === 'js') || hasExtensions(includeAssetString, jsExtensions)) {
            if (assets.js.indexOf(includeAssetString) === -1 && jsAssets.indexOf(includeAssetString) === -1) {
              jsAssets.push(includeAssetString);
            }
          } else if ((includeAssetType && includeAssetType === 'css') || hasExtensions(includeAssetString, cssExtensions)) {
            if (assets.css.indexOf(includeAssetString) === -1 && cssAssets.indexOf(includeAssetString) === -1) {
              cssAssets.push(includeAssetString);
            }
          }
        }
      }
      if (appendAssets) {
        assets.js = assets.js.concat(jsAssets);
        assets.css = assets.css.concat(cssAssets);
      } else {
        assets.js = jsAssets.concat(assets.js);
        assets.css = cssAssets.concat(assets.css);
      }

      if (callback) {
        callback(null, htmlPluginData);
      } else {
        return Promise.resolve(htmlPluginData);
      }
    }

    function onAlterAssetTag (htmlPluginData, callback) {
      var tags;
      var tagCount;
      var tag;
      var includeAssets = self.options.assets;
      var assetAttributes;
      var findAttributesForAsset = function (assets, href) {
        var assetCount = assets.length;
        var asset;

        for (var i = 0; i < assetCount; i++) {
          asset = assets[i];
          if (!isString(asset) && asset.attributes && href === getAssetPath(asset.path)) {
            return asset.attributes;
          }
        }
        return null;
      };

      if (shouldSkip(htmlPluginData)) {
        if (callback) {
          return callback(null, htmlPluginData);
        } else {
          return Promise.resolve(htmlPluginData);
        }
      }

      tags = htmlPluginData.head.concat(htmlPluginData.body);
      tagCount = tags.length;
      for (var i = 0; i < tagCount; i++) {
        tag = tags[i];
        assetAttributes = findAttributesForAsset(includeAssets, tag.attributes.href || tag.attributes.src);
        if (assetAttributes) {
          extend(tag.attributes, assetAttributes);
        }
      }

      if (callback) {
        callback(null, htmlPluginData);
      } else {
        return Promise.resolve(htmlPluginData);
      }
    }

    // Webpack 4+
    if (compilation.hooks) {
      compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration.tapAsync('htmlWebpackIncludeAssetsPlugin', onBeforeHtmlGeneration);
      compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('htmlWebpackIncludeAssetsPlugin', onAlterAssetTag);
    } else {
      // Webpack 3
      compilation.plugin('html-webpack-plugin-before-html-generation', onBeforeHtmlGeneration);
      compilation.plugin('html-webpack-plugin-alter-asset-tags', onAlterAssetTag);
    }
  }

  // Webpack 4+
  if (compiler.hooks) {
    compiler.hooks.compilation.tap('htmlWebpackIncludeAssetsPlugin', onCompilation);
  } else {
    // Webpack 3
    compiler.plugin('compilation', onCompilation);
  }
};

module.exports = HtmlWebpackIncludeAssetsPlugin;
<|endoftext|>


<commit_before>// Copyright (c) 2015 - 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

/* eslint-disable guard-for-in */
import Attribute from './attribute';
import log from '../utils/log';

import AttributeTransitionManager from './attribute-transition-manager';

const LOG_START_END_PRIORITY = 2;
const LOG_DETAIL_PRIORITY = 3;

function noop() {}

// Default loggers
const logFunctions = {
  savedMessages: null,
  timeStart: null,
  onLog: ({level, message}) => {
    log.log(level, message)();
  },
  onUpdateStart: ({level, numInstances}) => {
    logFunctions.savedMessages = [];
    logFunctions.timeStart = new Date();
  },
  onUpdate: ({level, message}) => {
    if (logFunctions.savedMessages) {
      logFunctions.savedMessages.push(message);
    }
  },
  onUpdateEnd: ({level, id, numInstances}) => {
    const timeMs = Math.round(new Date() - logFunctions.timeStart);
    const time = `${timeMs}ms`;
    log.group(level, `Updated attributes for ${numInstances} instances in ${id} in ${time}`, {
      collapsed: true
    })();
    for (const message of logFunctions.savedMessages) {
      log.log(level, message)();
    }
    log.groupEnd(level, `Updated attributes for ${numInstances} instances in ${id} in ${time}`)();
    logFunctions.savedMessages = null;
  }
};

export default class AttributeManager {
  /**
   * Sets log functions to help trace or time attribute updates.
   * Default logging uses deck logger.
   *
   * `onLog` is called for each attribute.
   *
   * To enable detailed control of timming and e.g. hierarchical logging,
   * hooks are also provided for update start and end.
   *
   * @param {Object} [opts]
   * @param {String} [onLog=] - called to print
   * @param {String} [onUpdateStart=] - called before update() starts
   * @param {String} [onUpdateEnd=] - called after update() ends
   */
  static setDefaultLogFunctions({onLog, onUpdateStart, onUpdate, onUpdateEnd} = {}) {
    if (onLog !== undefined) {
      logFunctions.onLog = onLog || noop;
    }
    if (onUpdateStart !== undefined) {
      logFunctions.onUpdateStart = onUpdateStart || noop;
    }
    if (onUpdate !== undefined) {
      logFunctions.onUpdate = onUpdate || noop;
    }
    if (onUpdateEnd !== undefined) {
      logFunctions.onUpdateEnd = onUpdateEnd || noop;
    }
  }

  /**
   * @classdesc
   * Automated attribute generation and management. Suitable when a set of
   * vertex shader attributes are generated by iteration over a data array,
   * and updates to these attributes are needed either when the data itself
   * changes, or when other data relevant to the calculations change.
   *
   * - First the application registers descriptions of its dynamic vertex
   *   attributes using AttributeManager.add().
   * - Then, when any change that affects attributes is detected by the
   *   application, the app will call AttributeManager.invalidate().
   * - Finally before it renders, it calls AttributeManager.update() to
   *   ensure that attributes are automatically rebuilt if anything has been
   *   invalidated.
   *
   * The application provided update functions describe how attributes
   * should be updated from a data array and are expected to traverse
   * that data array (or iterable) and fill in the attribute's typed array.
   *
   * Note that the attribute manager intentionally does not do advanced
   * change detection, but instead makes it easy to build such detection
   * by offering the ability to "invalidate" each attribute separately.
   */
  constructor(gl, {id = 'attribute-manager', stats} = {}) {
    this.id = id;
    this.gl = gl;

    this.attributes = {};

    this.updateTriggers = {};
    this.accessors = {};
    this.needsRedraw = true;

    this.userData = {};
    this.stats = stats;

    this.attributeTransitionManager = new AttributeTransitionManager(gl, {
      id: `${id}-transitions`
    });

    // For debugging sanity, prevent uninitialized members
    Object.seal(this);
  }

  finalize() {
    for (const attributeName in this.attributes) {
      this.attributes[attributeName].delete();
    }
    this.attributeTransitionManager.finalize();
  }

  // Returns the redraw flag, optionally clearing it.
  // Redraw flag will be set if any attributes attributes changed since
  // flag was last cleared.
  //
  // @param {String} [clearRedrawFlags=false] - whether to clear the flag
  // @return {false|String} - reason a redraw is needed.
  getNeedsRedraw({clearRedrawFlags = false} = {}) {
    const redraw = this.needsRedraw;
    this.needsRedraw = this.needsRedraw && !clearRedrawFlags;
    return redraw && this.id;
  }

  // Sets the redraw flag.
  // @param {Boolean} redraw=true
  // @return {AttributeManager} - for chaining
  setNeedsRedraw(redraw = true) {
    this.needsRedraw = true;
    return this;
  }

  // Adds attributes
  add(attributes, updaters = {}) {
    this._add(attributes, updaters);
  }

  // Adds attributes
  addInstanced(attributes, updaters = {}) {
    this._add(attributes, updaters, {instanced: 1});
  }

  /**
   * Removes attributes
   * Takes an array of attribute names and delete them from
   * the attribute map if they exists
   *
   * @example
   * attributeManager.remove(['position']);
   *
   * @param {Object} attributeNameArray - attribute name array (see above)
   */
  remove(attributeNameArray) {
    for (let i = 0; i < attributeNameArray.length; i++) {
      const name = attributeNameArray[i];
      if (this.attributes[name] !== undefined) {
        this.attributes[name].delete();
        delete this.attributes[name];
      }
    }
  }

  // Marks an attribute for update
  invalidate(triggerName) {
    const invalidatedAttributes = this._invalidateTrigger(triggerName);
    // For performance tuning
    logFunctions.onLog({
      level: LOG_DETAIL_PRIORITY,
      message: `invalidated attributes ${invalidatedAttributes} (${triggerName}) for ${this.id}`
    });
  }

  invalidateAll() {
    for (const attributeName in this.attributes) {
      this.attributes[attributeName].setNeedsUpdate();
    }
    // For performance tuning
    logFunctions.onLog({
      level: LOG_DETAIL_PRIORITY,
      message: `invalidated all attributes for ${this.id}`
    });
  }

  // Ensure all attribute buffers are updated from props or data.
  update({data, numInstances, transitions, props = {}, buffers = {}, context = {}} = {}) {
    // keep track of whether some attributes are updated
    let updated = false;

    logFunctions.onUpdateStart({level: LOG_START_END_PRIORITY, id: this.id, numInstances});
    if (this.stats) {
      this.stats.timeStart('attribute updates', this.id);
    }

    for (const attributeName in this.attributes) {
      const attribute = this.attributes[attributeName];

      if (attribute.setExternalBuffer(buffers[attributeName], this.numInstances)) {
        // Attribute is using external buffer from the props
      } else if (attribute.setGenericValue(props[attribute.getAccessor()])) {
        // Attribute is using generic value from the props
      } else if (attribute.needsUpdate()) {
        updated = true;
        this._updateAttribute({attribute, numInstances, data, props, context});
      }

      this.needsRedraw |= attribute.needsRedraw();
    }

    if (updated) {
      // Only initiate alloc/update (and logging) if actually needed
      if (this.stats) {
        this.stats.timeEnd('attribute updates', this.id);
      }
      logFunctions.onUpdateEnd({level: LOG_START_END_PRIORITY, id: this.id, numInstances});
    }

    this.attributeTransitionManager.update({
      attributes: this.attributes,
      numInstances,
      transitions
    });
  }

  // Update attribute transition to the current timestamp
  // Returns `true` if any transition is in progress
  updateTransition() {
    const {attributeTransitionManager} = this;
    const transitionUpdated = attributeTransitionManager.setCurrentTime(Date.now());
    this.needsRedraw = this.needsRedraw || transitionUpdated;
    return transitionUpdated;
  }

  /**
   * Returns all attribute descriptors
   * Note: Format matches luma.gl Model/Program.setAttributes()
   * @return {Object} attributes - descriptors
   */
  getAttributes() {
    return this.attributes;
  }

  /**
   * Returns changed attribute descriptors
   * This indicates which WebGLBuggers need to be updated
   * @return {Object} attributes - descriptors
   */
  getChangedAttributes({clearChangedFlags = false}) {
    const {attributes, attributeTransitionManager} = this;

    const changedAttributes = Object.assign({}, attributeTransitionManager.getAttributes());

    for (const attributeName in attributes) {
      const attribute = attributes[attributeName];
      if (attribute.needsRedraw({clearChangedFlags: true})) {
        // Only return non-transition attributes
        if (!attributeTransitionManager.hasAttribute(attributeName)) {
          changedAttributes[attributeName] = attribute;
        }
      }
    }
    return changedAttributes;
  }

  // PROTECTED METHODS - Only to be used by collaborating classes, not by apps

  // Returns object containing all accessors as keys, with non-null values
  // @return {Object} - accessors object
  getAccessors() {
    return this.updateTriggers;
  }

  // PRIVATE METHODS

  // Used to register an attribute
  _add(attributes, updaters, extraProps = {}) {
    if (updaters) {
      log.removed('AttributeManager.add({updaters}) - updater map no longer supported');
    }

    const newAttributes = {};

    for (const attributeName in attributes) {
      const attribute = attributes[attributeName];

      // Initialize the attribute descriptor, with WebGL and metadata fields
      newAttributes[attributeName] = new Attribute(
        this.gl,
        Object.assign({}, attribute, {
          id: attributeName,
          // Luma fields
          constant: attribute.constant || false,
          isIndexed: attribute.isIndexed || attribute.elements,
          size: (attribute.elements && 1) || attribute.size,
          value: attribute.value || null,
          instanced: attribute.instanced || extraProps.instanced
        })
      );
    }

    Object.assign(this.attributes, newAttributes);

    this._mapUpdateTriggersToAttributes();
  }
  /* eslint-enable max-statements */

  // build updateTrigger name to attribute name mapping
  _mapUpdateTriggersToAttributes() {
    const triggers = {};

    for (const attributeName in this.attributes) {
      const attribute = this.attributes[attributeName];
      attribute.getUpdateTriggers().forEach(triggerName => {
        if (!triggers[triggerName]) {
          triggers[triggerName] = [];
        }
        triggers[triggerName].push(attributeName);
      });
    }

    this.updateTriggers = triggers;
  }

  _invalidateTrigger(triggerName) {
    const {attributes, updateTriggers} = this;
    const invalidatedAttributes = updateTriggers[triggerName];

    if (!invalidatedAttributes) {
      let message = `invalidating non-existent trigger ${triggerName} for ${this.id}\n`;
      message += `Valid triggers: ${Object.keys(attributes).join(', ')}`;
      log.warn(message, invalidatedAttributes)();
    } else {
      invalidatedAttributes.forEach(name => {
        const attribute = attributes[name];
        if (attribute) {
          attribute.setNeedsUpdate();
        }
      });
    }
    return invalidatedAttributes;
  }

  _updateAttribute({attribute, numInstances, data, props, context}) {
    if (attribute.allocate(numInstances)) {
      logFunctions.onUpdate({
        level: LOG_DETAIL_PRIORITY,
        message: `${attribute.id} allocated ${numInstances}`,
        id: this.id
      });
    }

    // Calls update on any buffers that need update
    const timeStart = Date.now();

    const updated = attribute.updateBuffer({numInstances, data, props, context});
    if (updated) {
      this.needsRedraw = true;

      const timeMs = Math.round(Date.now() - timeStart);
      logFunctions.onUpdate({
        level: LOG_DETAIL_PRIORITY,
        message: `${attribute.id} updated ${numInstances} in ${timeMs}ms`
      });
    }
  }
}
<commit_msg>disable updateTrigger warning (#2036)<commit_after>// Copyright (c) 2015 - 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

/* eslint-disable guard-for-in */
import Attribute from './attribute';
import log from '../utils/log';

import AttributeTransitionManager from './attribute-transition-manager';

const LOG_START_END_PRIORITY = 2;
const LOG_DETAIL_PRIORITY = 3;

function noop() {}

// Default loggers
const logFunctions = {
  savedMessages: null,
  timeStart: null,
  onLog: ({level, message}) => {
    log.log(level, message)();
  },
  onUpdateStart: ({level, numInstances}) => {
    logFunctions.savedMessages = [];
    logFunctions.timeStart = new Date();
  },
  onUpdate: ({level, message}) => {
    if (logFunctions.savedMessages) {
      logFunctions.savedMessages.push(message);
    }
  },
  onUpdateEnd: ({level, id, numInstances}) => {
    const timeMs = Math.round(new Date() - logFunctions.timeStart);
    const time = `${timeMs}ms`;
    log.group(level, `Updated attributes for ${numInstances} instances in ${id} in ${time}`, {
      collapsed: true
    })();
    for (const message of logFunctions.savedMessages) {
      log.log(level, message)();
    }
    log.groupEnd(level, `Updated attributes for ${numInstances} instances in ${id} in ${time}`)();
    logFunctions.savedMessages = null;
  }
};

export default class AttributeManager {
  /**
   * Sets log functions to help trace or time attribute updates.
   * Default logging uses deck logger.
   *
   * `onLog` is called for each attribute.
   *
   * To enable detailed control of timming and e.g. hierarchical logging,
   * hooks are also provided for update start and end.
   *
   * @param {Object} [opts]
   * @param {String} [onLog=] - called to print
   * @param {String} [onUpdateStart=] - called before update() starts
   * @param {String} [onUpdateEnd=] - called after update() ends
   */
  static setDefaultLogFunctions({onLog, onUpdateStart, onUpdate, onUpdateEnd} = {}) {
    if (onLog !== undefined) {
      logFunctions.onLog = onLog || noop;
    }
    if (onUpdateStart !== undefined) {
      logFunctions.onUpdateStart = onUpdateStart || noop;
    }
    if (onUpdate !== undefined) {
      logFunctions.onUpdate = onUpdate || noop;
    }
    if (onUpdateEnd !== undefined) {
      logFunctions.onUpdateEnd = onUpdateEnd || noop;
    }
  }

  /**
   * @classdesc
   * Automated attribute generation and management. Suitable when a set of
   * vertex shader attributes are generated by iteration over a data array,
   * and updates to these attributes are needed either when the data itself
   * changes, or when other data relevant to the calculations change.
   *
   * - First the application registers descriptions of its dynamic vertex
   *   attributes using AttributeManager.add().
   * - Then, when any change that affects attributes is detected by the
   *   application, the app will call AttributeManager.invalidate().
   * - Finally before it renders, it calls AttributeManager.update() to
   *   ensure that attributes are automatically rebuilt if anything has been
   *   invalidated.
   *
   * The application provided update functions describe how attributes
   * should be updated from a data array and are expected to traverse
   * that data array (or iterable) and fill in the attribute's typed array.
   *
   * Note that the attribute manager intentionally does not do advanced
   * change detection, but instead makes it easy to build such detection
   * by offering the ability to "invalidate" each attribute separately.
   */
  constructor(gl, {id = 'attribute-manager', stats} = {}) {
    this.id = id;
    this.gl = gl;

    this.attributes = {};

    this.updateTriggers = {};
    this.accessors = {};
    this.needsRedraw = true;

    this.userData = {};
    this.stats = stats;

    this.attributeTransitionManager = new AttributeTransitionManager(gl, {
      id: `${id}-transitions`
    });

    // For debugging sanity, prevent uninitialized members
    Object.seal(this);
  }

  finalize() {
    for (const attributeName in this.attributes) {
      this.attributes[attributeName].delete();
    }
    this.attributeTransitionManager.finalize();
  }

  // Returns the redraw flag, optionally clearing it.
  // Redraw flag will be set if any attributes attributes changed since
  // flag was last cleared.
  //
  // @param {String} [clearRedrawFlags=false] - whether to clear the flag
  // @return {false|String} - reason a redraw is needed.
  getNeedsRedraw({clearRedrawFlags = false} = {}) {
    const redraw = this.needsRedraw;
    this.needsRedraw = this.needsRedraw && !clearRedrawFlags;
    return redraw && this.id;
  }

  // Sets the redraw flag.
  // @param {Boolean} redraw=true
  // @return {AttributeManager} - for chaining
  setNeedsRedraw(redraw = true) {
    this.needsRedraw = true;
    return this;
  }

  // Adds attributes
  add(attributes, updaters = {}) {
    this._add(attributes, updaters);
  }

  // Adds attributes
  addInstanced(attributes, updaters = {}) {
    this._add(attributes, updaters, {instanced: 1});
  }

  /**
   * Removes attributes
   * Takes an array of attribute names and delete them from
   * the attribute map if they exists
   *
   * @example
   * attributeManager.remove(['position']);
   *
   * @param {Object} attributeNameArray - attribute name array (see above)
   */
  remove(attributeNameArray) {
    for (let i = 0; i < attributeNameArray.length; i++) {
      const name = attributeNameArray[i];
      if (this.attributes[name] !== undefined) {
        this.attributes[name].delete();
        delete this.attributes[name];
      }
    }
  }

  // Marks an attribute for update
  invalidate(triggerName) {
    const invalidatedAttributes = this._invalidateTrigger(triggerName);
    // For performance tuning
    logFunctions.onLog({
      level: LOG_DETAIL_PRIORITY,
      message: `invalidated attributes ${invalidatedAttributes} (${triggerName}) for ${this.id}`
    });
  }

  invalidateAll() {
    for (const attributeName in this.attributes) {
      this.attributes[attributeName].setNeedsUpdate();
    }
    // For performance tuning
    logFunctions.onLog({
      level: LOG_DETAIL_PRIORITY,
      message: `invalidated all attributes for ${this.id}`
    });
  }

  // Ensure all attribute buffers are updated from props or data.
  update({data, numInstances, transitions, props = {}, buffers = {}, context = {}} = {}) {
    // keep track of whether some attributes are updated
    let updated = false;

    logFunctions.onUpdateStart({level: LOG_START_END_PRIORITY, id: this.id, numInstances});
    if (this.stats) {
      this.stats.timeStart('attribute updates', this.id);
    }

    for (const attributeName in this.attributes) {
      const attribute = this.attributes[attributeName];

      if (attribute.setExternalBuffer(buffers[attributeName], this.numInstances)) {
        // Attribute is using external buffer from the props
      } else if (attribute.setGenericValue(props[attribute.getAccessor()])) {
        // Attribute is using generic value from the props
      } else if (attribute.needsUpdate()) {
        updated = true;
        this._updateAttribute({attribute, numInstances, data, props, context});
      }

      this.needsRedraw |= attribute.needsRedraw();
    }

    if (updated) {
      // Only initiate alloc/update (and logging) if actually needed
      if (this.stats) {
        this.stats.timeEnd('attribute updates', this.id);
      }
      logFunctions.onUpdateEnd({level: LOG_START_END_PRIORITY, id: this.id, numInstances});
    }

    this.attributeTransitionManager.update({
      attributes: this.attributes,
      numInstances,
      transitions
    });
  }

  // Update attribute transition to the current timestamp
  // Returns `true` if any transition is in progress
  updateTransition() {
    const {attributeTransitionManager} = this;
    const transitionUpdated = attributeTransitionManager.setCurrentTime(Date.now());
    this.needsRedraw = this.needsRedraw || transitionUpdated;
    return transitionUpdated;
  }

  /**
   * Returns all attribute descriptors
   * Note: Format matches luma.gl Model/Program.setAttributes()
   * @return {Object} attributes - descriptors
   */
  getAttributes() {
    return this.attributes;
  }

  /**
   * Returns changed attribute descriptors
   * This indicates which WebGLBuggers need to be updated
   * @return {Object} attributes - descriptors
   */
  getChangedAttributes({clearChangedFlags = false}) {
    const {attributes, attributeTransitionManager} = this;

    const changedAttributes = Object.assign({}, attributeTransitionManager.getAttributes());

    for (const attributeName in attributes) {
      const attribute = attributes[attributeName];
      if (attribute.needsRedraw({clearChangedFlags: true})) {
        // Only return non-transition attributes
        if (!attributeTransitionManager.hasAttribute(attributeName)) {
          changedAttributes[attributeName] = attribute;
        }
      }
    }
    return changedAttributes;
  }

  // PROTECTED METHODS - Only to be used by collaborating classes, not by apps

  // Returns object containing all accessors as keys, with non-null values
  // @return {Object} - accessors object
  getAccessors() {
    return this.updateTriggers;
  }

  // PRIVATE METHODS

  // Used to register an attribute
  _add(attributes, updaters, extraProps = {}) {
    if (updaters) {
      log.removed('AttributeManager.add({updaters}) - updater map no longer supported');
    }

    const newAttributes = {};

    for (const attributeName in attributes) {
      const attribute = attributes[attributeName];

      // Initialize the attribute descriptor, with WebGL and metadata fields
      newAttributes[attributeName] = new Attribute(
        this.gl,
        Object.assign({}, attribute, {
          id: attributeName,
          // Luma fields
          constant: attribute.constant || false,
          isIndexed: attribute.isIndexed || attribute.elements,
          size: (attribute.elements && 1) || attribute.size,
          value: attribute.value || null,
          instanced: attribute.instanced || extraProps.instanced
        })
      );
    }

    Object.assign(this.attributes, newAttributes);

    this._mapUpdateTriggersToAttributes();
  }
  /* eslint-enable max-statements */

  // build updateTrigger name to attribute name mapping
  _mapUpdateTriggersToAttributes() {
    const triggers = {};

    for (const attributeName in this.attributes) {
      const attribute = this.attributes[attributeName];
      attribute.getUpdateTriggers().forEach(triggerName => {
        if (!triggers[triggerName]) {
          triggers[triggerName] = [];
        }
        triggers[triggerName].push(attributeName);
      });
    }

    this.updateTriggers = triggers;
  }

  _invalidateTrigger(triggerName) {
    const {attributes, updateTriggers} = this;
    const invalidatedAttributes = updateTriggers[triggerName];

    if (invalidatedAttributes) {
      invalidatedAttributes.forEach(name => {
        const attribute = attributes[name];
        if (attribute) {
          attribute.setNeedsUpdate();
        }
      });
    } else {
      // let message = `invalidating non-existent trigger ${triggerName} for ${this.id}\n`;
      // message += `Valid triggers: ${Object.keys(attributes).join(', ')}`;
      // log.warn(message, invalidatedAttributes)();
    }
    return invalidatedAttributes;
  }

  _updateAttribute({attribute, numInstances, data, props, context}) {
    if (attribute.allocate(numInstances)) {
      logFunctions.onUpdate({
        level: LOG_DETAIL_PRIORITY,
        message: `${attribute.id} allocated ${numInstances}`,
        id: this.id
      });
    }

    // Calls update on any buffers that need update
    const timeStart = Date.now();

    const updated = attribute.updateBuffer({numInstances, data, props, context});
    if (updated) {
      this.needsRedraw = true;

      const timeMs = Math.round(Date.now() - timeStart);
      logFunctions.onUpdate({
        level: LOG_DETAIL_PRIORITY,
        message: `${attribute.id} updated ${numInstances} in ${timeMs}ms`
      });
    }
  }
}
<|endoftext|>