<commit_before>var inherits = require('util').inherits;
var Unit = require('../units').Unit;

var Freq = require('./freq');

var SocketStat = function () {
	this.freq = null;
	this.per = null;
	this.connections = null;
};
inherits(SocketStat, Unit);

SocketStat.prototype.unitInit = function (units) {
	this.connections = units.get('core.connections');
	var coreSettings = units.require('core.settings').core;
	var mechanics = units.require('core.lowlevel').socket;

	var frameSize = null;
	if (coreSettings.stat && coreSettings.stat.socket)
	{
		var statSettings = coreSettings.stat.socket;
		if (statSettings.frameSize != null)
		{
			frameSize = statSettings.frameSize;
		}
		this.per = statSettings.per;
	}
	this.freq = new Freq(frameSize);

	if (mechanics != null)
	{
		this.freq.start();

		var self = this;
		mechanics.on('connect', function (connection) {
			self.freq.update('connect');
		});
		mechanics.on('disconnect', function (connection) {
			self.freq.update('disconnect');
		});
		mechanics.on('message', function (connection, message) {
			self.freq.update('messageIn');
		});
		mechanics.transport.on('message_sent', function (connection, message) {
			self.freq.update('messageOut');
		});
	}
};

SocketStat.prototype.getStat = function () {
	var result = {};

	this.updateResult(result, 'connections', this.getConnectionsStat());
	this.updateResult(result, 'socket', this.getSocketStat());

	return result;
};

SocketStat.prototype.updateResult = function (result, key, value) {
	if (value != null)
	{
		result[key] = value;
	}
};

SocketStat.prototype.getConnectionsStat = function () {
	var result = null;

	var connections = this.connections;
	if (connections != null)
	{
		result = {
			counts: {
				connection: connections.connectionCount,
				user: connections.userConnections.count,
				endpoint: connections.endpointConnections.count
			}
		};

		var noEndpointCount = 0;
		var byTransport = {};
		for (var k in connections.connections)
		{
			var connection = connections.connections[k];

			var endpointInfo = connections.getEndpoint(connection);
			if (endpointInfo == null || endpointInfo.endpointId == null)
			{
				noEndpointCount++;
			}

			var protocol = this.getProtocol(connection);
			var count = byTransport[protocol];
			byTransport[protocol] = (count == null ? 1 : count + 1);
		}

		result.counts.connectionWithoutEndpoint = noEndpointCount;
		result.counts.byTransport = byTransport;

		var endpointConnectionsStat = this.getEndpointConnectionsStat(connections.endpointConnections);
		if (endpointConnectionsStat != null)
		{
			result.counts.endpointWithManyConnections = endpointConnectionsStat;
		}
	}

	return result;
};

SocketStat.prototype.getEndpointConnectionsStat = function (endpointConnections) {
	var result = {
		count: 0
	};
	var transports = {};
	var found = false;
	for (var k in endpointConnections.dict)
	{
		var singleEndpointConnections = endpointConnections.get(k);
		var count = 0;
		var byTransport = {};
		for (var j in singleEndpointConnections)
		{
			count++;
			var connection = singleEndpointConnections[j];
			var protocol = this.getProtocol(connection);
			var byTransportCount = byTransport[protocol];
			byTransport[protocol] = (byTransportCount == null ? 1 : byTransportCount + 1);
		}
		if (count > 0)
		{
			found = true;
			result.count++;
			var hash = this.getTransportsHash(byTransport);
			var byHash = transports[hash];
			if (byHash == null)
			{
				transports[hash] = {
					transports: byTransport,
					count: 1
				};
			}
			else
			{
				byHash.count++;
			}
		}
	}
	if (found)
	{
		var combinations = [];
		for (var i in transports)
		{
			combinations.push(transports[i]);
		}
		result.combinations = combinations;
	}
	else
	{
		result = null;
	}
	return result;
};

SocketStat.prototype.getTransportsHash = function (transportCounts) {
	var arr = [];
	for (var transport in transportCounts)
	{
		arr.push([
			transport,
			transportCounts[transport]
		].join('\n'));
	}
	return arr.sort().join('\n\n');
};

SocketStat.prototype.getProtocol = function (connection) {
	// TODO replace code below with connection.protocol when sockjs will be updated
	var recv = connection._session.recv;
	var protocol = null;
	if (recv != null)
	{
		protocol = recv.protocol;
	}
	return protocol || 'unknown';
};

SocketStat.prototype.getSocketStat = function () {
	return {
		frameSize: this.freq.frameSize,
		per: this.per == null ? 1000 : this.per,
		freq: {
			connect: this.freq.getFreqNorm('connect'),
			disconnect: this.freq.getFreqNorm('disconnect'),
			messageIn: this.freq.getFreqNorm('messageIn'),
			messageOut: this.freq.getFreqNorm('messageOut')
		}
	};
};

module.exports = SocketStat;
<commit_msg>fixed minor bug in stat (showing endpointWithManyConnections block even if thare are none)<commit_after>var inherits = require('util').inherits;
var Unit = require('../units').Unit;

var Freq = require('./freq');

var SocketStat = function () {
	this.freq = null;
	this.per = null;
	this.connections = null;
};
inherits(SocketStat, Unit);

SocketStat.prototype.unitInit = function (units) {
	this.connections = units.get('core.connections');
	var coreSettings = units.require('core.settings').core;
	var mechanics = units.require('core.lowlevel').socket;

	var frameSize = null;
	if (coreSettings.stat && coreSettings.stat.socket)
	{
		var statSettings = coreSettings.stat.socket;
		if (statSettings.frameSize != null)
		{
			frameSize = statSettings.frameSize;
		}
		this.per = statSettings.per;
	}
	this.freq = new Freq(frameSize);

	if (mechanics != null)
	{
		this.freq.start();

		var self = this;
		mechanics.on('connect', function (connection) {
			self.freq.update('connect');
		});
		mechanics.on('disconnect', function (connection) {
			self.freq.update('disconnect');
		});
		mechanics.on('message', function (connection, message) {
			self.freq.update('messageIn');
		});
		mechanics.transport.on('message_sent', function (connection, message) {
			self.freq.update('messageOut');
		});
	}
};

SocketStat.prototype.getStat = function () {
	var result = {};

	this.updateResult(result, 'connections', this.getConnectionsStat());
	this.updateResult(result, 'socket', this.getSocketStat());

	return result;
};

SocketStat.prototype.updateResult = function (result, key, value) {
	if (value != null)
	{
		result[key] = value;
	}
};

SocketStat.prototype.getConnectionsStat = function () {
	var result = null;

	var connections = this.connections;
	if (connections != null)
	{
		result = {
			counts: {
				connection: connections.connectionCount,
				user: connections.userConnections.count,
				endpoint: connections.endpointConnections.count
			}
		};

		var noEndpointCount = 0;
		var byTransport = {};
		for (var k in connections.connections)
		{
			var connection = connections.connections[k];

			var endpointInfo = connections.getEndpoint(connection);
			if (endpointInfo == null || endpointInfo.endpointId == null)
			{
				noEndpointCount++;
			}

			var protocol = this.getProtocol(connection);
			var count = byTransport[protocol];
			byTransport[protocol] = (count == null ? 1 : count + 1);
		}

		result.counts.connectionWithoutEndpoint = noEndpointCount;
		result.counts.byTransport = byTransport;

		var endpointConnectionsStat = this.getEndpointConnectionsStat(connections.endpointConnections);
		if (endpointConnectionsStat != null)
		{
			result.counts.endpointWithManyConnections = endpointConnectionsStat;
		}
	}

	return result;
};

SocketStat.prototype.getEndpointConnectionsStat = function (endpointConnections) {
	var result = {
		count: 0
	};
	var transports = {};
	var found = false;
	for (var k in endpointConnections.dict)
	{
		var singleEndpointConnections = endpointConnections.get(k);
		var count = 0;
		var byTransport = {};
		for (var j in singleEndpointConnections)
		{
			count++;
			var connection = singleEndpointConnections[j];
			var protocol = this.getProtocol(connection);
			var byTransportCount = byTransport[protocol];
			byTransport[protocol] = (byTransportCount == null ? 1 : byTransportCount + 1);
		}
		if (count > 1)
		{
			found = true;
			result.count++;
			var hash = this.getTransportsHash(byTransport);
			var byHash = transports[hash];
			if (byHash == null)
			{
				transports[hash] = {
					transports: byTransport,
					count: 1
				};
			}
			else
			{
				byHash.count++;
			}
		}
	}
	if (found)
	{
		var combinations = [];
		for (var i in transports)
		{
			combinations.push(transports[i]);
		}
		result.combinations = combinations;
	}
	else
	{
		result = null;
	}
	return result;
};

SocketStat.prototype.getTransportsHash = function (transportCounts) {
	var arr = [];
	for (var transport in transportCounts)
	{
		arr.push([
			transport,
			transportCounts[transport]
		].join('\n'));
	}
	return arr.sort().join('\n\n');
};

SocketStat.prototype.getProtocol = function (connection) {
	// TODO replace code below with connection.protocol when sockjs will be updated
	var recv = connection._session.recv;
	var protocol = null;
	if (recv != null)
	{
		protocol = recv.protocol;
	}
	return protocol || 'unknown';
};

SocketStat.prototype.getSocketStat = function () {
	return {
		frameSize: this.freq.frameSize,
		per: this.per == null ? 1000 : this.per,
		freq: {
			connect: this.freq.getFreqNorm('connect'),
			disconnect: this.freq.getFreqNorm('disconnect'),
			messageIn: this.freq.getFreqNorm('messageIn'),
			messageOut: this.freq.getFreqNorm('messageOut')
		}
	};
};

module.exports = SocketStat;
<|endoftext|>


<commit_before>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * Enum for WebDriver status codes.
 * @enum {number}
 */
var StatusCode = {
  STALE_ELEMENT_REFERENCE: 10,
  UNKNOWN_ERROR: 13,
};

/**
 * Enum for node types.
 * @enum {number}
 */
var NodeType = {
  ELEMENT: 1,
  DOCUMENT: 9,
};

/**
 * Dictionary key to use for holding an element ID.
 * @const
 * @type {string}
 */
var ELEMENT_KEY = 'ELEMENT';

/**
 * True if shadow dom is enabled.
 * @const
 * @type {boolean}
 */
var SHADOW_DOM_ENABLED = typeof WebKitShadowRoot === 'function';

/**
 * A cache which maps IDs <-> cached objects for the purpose of identifying
 * a script object remotely.
 * @constructor
 */
function Cache() {
  this.cache_ = {};
  this.nextId_ = 1;
  this.idPrefix_ = Math.random().toString();
}

Cache.prototype = {

  /**
   * Stores a given item in the cache and returns a unique ID.
   *
   * @param {!Object} item The item to store in the cache.
   * @return {number} The ID for the cached item.
   */
  storeItem: function(item) {
    for (var i in this.cache_) {
      if (item == this.cache_[i])
        return i;
    }
    var id = this.idPrefix_  + '-' + this.nextId_;
    this.cache_[id] = item;
    this.nextId_++;
    return id;
  },

  /**
   * Retrieves the cached object for the given ID.
   *
   * @param {number} id The ID for the cached item to retrieve.
   * @return {!Object} The retrieved item.
   */
  retrieveItem: function(id) {
    var item = this.cache_[id];
    if (item)
      return item;
    var error = new Error('not in cache');
    error.code = StatusCode.STALE_ELEMENT_REFERENCE;
    error.message = 'element is not attached to the page document';
    throw error;
  },

  /**
   * Clears stale items from the cache.
   */
  clearStale: function() {
    for (var id in this.cache_) {
      var node = this.cache_[id];
      if (!this.isNodeReachable_(node))
        delete this.cache_[id];
    }
  },

  /**
    * @private
    * @param {!Node} node The node to check.
    * @return {boolean} If the nodes is reachable.
    */
  isNodeReachable_: function(node) {
    var nodeRoot = getNodeRoot(node);
    if (nodeRoot == document)
      return true;
    else if (SHADOW_DOM_ENABLED && nodeRoot instanceof WebKitShadowRoot)
      return true;

    return false;
  }
};

/**
 * Returns the root element of the node.  Found by traversing parentNodes until
 * a node with no parent is found.  This node is considered the root.
 * @param {!Node} node The node to find the root element for.
 * @return {!Node} The root node.
 */
function getNodeRoot(node) {
  while (node.parentNode) {
    node = node.parentNode;
  }
  return node;
}

/**
 * Returns the global object cache for the page.
 * @param {Document=} opt_doc The document whose cache to retrieve. Defaults to
 *     the current document.
 * @return {!Cache} The page's object cache.
 */
function getPageCache(opt_doc) {
  var doc = opt_doc || document;
  var key = '$cdc_asdjflasutopfhvcZLmcfl_';
  if (!(key in doc))
    doc[key] = new Cache();
  return doc[key];
}

/**
 * Wraps the given value to be transmitted remotely by converting
 * appropriate objects to cached object IDs.
 *
 * @param {*} value The value to wrap.
 * @return {*} The wrapped value.
 */
function wrap(value) {
  if (typeof(value) == 'object' && value != null) {
    var nodeType = value['nodeType'];
    if (nodeType == NodeType.ELEMENT || nodeType == NodeType.DOCUMENT
        || (SHADOW_DOM_ENABLED && value instanceof WebKitShadowRoot)) {
      var wrapped = {};
      var root = getNodeRoot(value);
      wrapped[ELEMENT_KEY] = getPageCache(root).storeItem(value);
      return wrapped;
    }

    var obj = (typeof(value.length) == 'number') ? [] : {};
    for (var prop in value)
      obj[prop] = wrap(value[prop]);
    return obj;
  }
  return value;
}

/**
 * Unwraps the given value by converting from object IDs to the cached
 * objects.
 *
 * @param {*} value The value to unwrap.
 * @param {Cache} cache The cache to retrieve wrapped elements from.
 * @return {*} The unwrapped value.
 */
function unwrap(value, cache) {
  if (typeof(value) == 'object' && value != null) {
    if (ELEMENT_KEY in value)
      return cache.retrieveItem(value[ELEMENT_KEY]);

    var obj = (typeof(value.length) == 'number') ? [] : {};
    for (var prop in value)
      obj[prop] = unwrap(value[prop], cache);
    return obj;
  }
  return value;
}

/**
 * Calls a given function and returns its value.
 *
 * The inputs to and outputs of the function will be unwrapped and wrapped
 * respectively, unless otherwise specified. This wrapping involves converting
 * between cached object reference IDs and actual JS objects. The cache will
 * automatically be pruned each call to remove stale references.
 *
 * @param  {Array.<string>} shadowHostIds The host ids of the nested shadow
 *     DOMs the function should be executed in the context of.
 * @param {function(...[*]) : *} func The function to invoke.
 * @param {!Array.<*>} args The array of arguments to supply to the function,
 *     which will be unwrapped before invoking the function.
 * @param {boolean=} opt_unwrappedReturn Whether the function's return value
 *     should be left unwrapped.
 * @return {*} An object containing a status and value property, where status
 *     is a WebDriver status code and value is the wrapped value. If an
 *     unwrapped return was specified, this will be the function's pure return
 *     value.
 */
function callFunction(shadowHostIds, func, args, opt_unwrappedReturn) {
  var cache = getPageCache();
  cache.clearStale();
  if (shadowHostIds && SHADOW_DOM_ENABLED) {
    for (var i = 0; i < shadowHostIds.length; i++) {
      var host = cache.retrieveItem(shadowHostIds[i]);
      // TODO(zachconrad): Use the olderShadowRoot API when available to check
      // all of the shadow roots.
      cache = getPageCache(host.webkitShadowRoot);
      cache.clearStale();
    }
  }

  if (opt_unwrappedReturn)
    return func.apply(null, unwrap(args, cache));

  var status = 0;
  try {
    var returnValue = wrap(func.apply(null, unwrap(args, cache)));
  } catch (error) {
    status = error.code || StatusCode.UNKNOWN_ERROR;
    var returnValue = error.message;
  }
  return {
      status: status,
      value: returnValue
  }
}
<commit_msg>Replace WebKitShadowRoot constructor with ShadowRoot constructor.<commit_after>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * Enum for WebDriver status codes.
 * @enum {number}
 */
var StatusCode = {
  STALE_ELEMENT_REFERENCE: 10,
  UNKNOWN_ERROR: 13,
};

/**
 * Enum for node types.
 * @enum {number}
 */
var NodeType = {
  ELEMENT: 1,
  DOCUMENT: 9,
};

/**
 * Dictionary key to use for holding an element ID.
 * @const
 * @type {string}
 */
var ELEMENT_KEY = 'ELEMENT';

/**
 * True if shadow dom is enabled.
 * @const
 * @type {boolean}
 */
var SHADOW_DOM_ENABLED = typeof ShadowRoot === 'function';

/**
 * A cache which maps IDs <-> cached objects for the purpose of identifying
 * a script object remotely.
 * @constructor
 */
function Cache() {
  this.cache_ = {};
  this.nextId_ = 1;
  this.idPrefix_ = Math.random().toString();
}

Cache.prototype = {

  /**
   * Stores a given item in the cache and returns a unique ID.
   *
   * @param {!Object} item The item to store in the cache.
   * @return {number} The ID for the cached item.
   */
  storeItem: function(item) {
    for (var i in this.cache_) {
      if (item == this.cache_[i])
        return i;
    }
    var id = this.idPrefix_  + '-' + this.nextId_;
    this.cache_[id] = item;
    this.nextId_++;
    return id;
  },

  /**
   * Retrieves the cached object for the given ID.
   *
   * @param {number} id The ID for the cached item to retrieve.
   * @return {!Object} The retrieved item.
   */
  retrieveItem: function(id) {
    var item = this.cache_[id];
    if (item)
      return item;
    var error = new Error('not in cache');
    error.code = StatusCode.STALE_ELEMENT_REFERENCE;
    error.message = 'element is not attached to the page document';
    throw error;
  },

  /**
   * Clears stale items from the cache.
   */
  clearStale: function() {
    for (var id in this.cache_) {
      var node = this.cache_[id];
      if (!this.isNodeReachable_(node))
        delete this.cache_[id];
    }
  },

  /**
    * @private
    * @param {!Node} node The node to check.
    * @return {boolean} If the nodes is reachable.
    */
  isNodeReachable_: function(node) {
    var nodeRoot = getNodeRoot(node);
    if (nodeRoot == document)
      return true;
    else if (SHADOW_DOM_ENABLED && nodeRoot instanceof ShadowRoot)
      return true;

    return false;
  }
};

/**
 * Returns the root element of the node.  Found by traversing parentNodes until
 * a node with no parent is found.  This node is considered the root.
 * @param {!Node} node The node to find the root element for.
 * @return {!Node} The root node.
 */
function getNodeRoot(node) {
  while (node.parentNode) {
    node = node.parentNode;
  }
  return node;
}

/**
 * Returns the global object cache for the page.
 * @param {Document=} opt_doc The document whose cache to retrieve. Defaults to
 *     the current document.
 * @return {!Cache} The page's object cache.
 */
function getPageCache(opt_doc) {
  var doc = opt_doc || document;
  var key = '$cdc_asdjflasutopfhvcZLmcfl_';
  if (!(key in doc))
    doc[key] = new Cache();
  return doc[key];
}

/**
 * Wraps the given value to be transmitted remotely by converting
 * appropriate objects to cached object IDs.
 *
 * @param {*} value The value to wrap.
 * @return {*} The wrapped value.
 */
function wrap(value) {
  if (typeof(value) == 'object' && value != null) {
    var nodeType = value['nodeType'];
    if (nodeType == NodeType.ELEMENT || nodeType == NodeType.DOCUMENT
        || (SHADOW_DOM_ENABLED && value instanceof ShadowRoot)) {
      var wrapped = {};
      var root = getNodeRoot(value);
      wrapped[ELEMENT_KEY] = getPageCache(root).storeItem(value);
      return wrapped;
    }

    var obj = (typeof(value.length) == 'number') ? [] : {};
    for (var prop in value)
      obj[prop] = wrap(value[prop]);
    return obj;
  }
  return value;
}

/**
 * Unwraps the given value by converting from object IDs to the cached
 * objects.
 *
 * @param {*} value The value to unwrap.
 * @param {Cache} cache The cache to retrieve wrapped elements from.
 * @return {*} The unwrapped value.
 */
function unwrap(value, cache) {
  if (typeof(value) == 'object' && value != null) {
    if (ELEMENT_KEY in value)
      return cache.retrieveItem(value[ELEMENT_KEY]);

    var obj = (typeof(value.length) == 'number') ? [] : {};
    for (var prop in value)
      obj[prop] = unwrap(value[prop], cache);
    return obj;
  }
  return value;
}

/**
 * Calls a given function and returns its value.
 *
 * The inputs to and outputs of the function will be unwrapped and wrapped
 * respectively, unless otherwise specified. This wrapping involves converting
 * between cached object reference IDs and actual JS objects. The cache will
 * automatically be pruned each call to remove stale references.
 *
 * @param  {Array.<string>} shadowHostIds The host ids of the nested shadow
 *     DOMs the function should be executed in the context of.
 * @param {function(...[*]) : *} func The function to invoke.
 * @param {!Array.<*>} args The array of arguments to supply to the function,
 *     which will be unwrapped before invoking the function.
 * @param {boolean=} opt_unwrappedReturn Whether the function's return value
 *     should be left unwrapped.
 * @return {*} An object containing a status and value property, where status
 *     is a WebDriver status code and value is the wrapped value. If an
 *     unwrapped return was specified, this will be the function's pure return
 *     value.
 */
function callFunction(shadowHostIds, func, args, opt_unwrappedReturn) {
  var cache = getPageCache();
  cache.clearStale();
  if (shadowHostIds && SHADOW_DOM_ENABLED) {
    for (var i = 0; i < shadowHostIds.length; i++) {
      var host = cache.retrieveItem(shadowHostIds[i]);
      // TODO(zachconrad): Use the olderShadowRoot API when available to check
      // all of the shadow roots.
      cache = getPageCache(host.webkitShadowRoot);
      cache.clearStale();
    }
  }

  if (opt_unwrappedReturn)
    return func.apply(null, unwrap(args, cache));

  var status = 0;
  try {
    var returnValue = wrap(func.apply(null, unwrap(args, cache)));
  } catch (error) {
    status = error.code || StatusCode.UNKNOWN_ERROR;
    var returnValue = error.message;
  }
  return {
      status: status,
      value: returnValue
  }
}
<|endoftext|>


<commit_before>(function($) {
    var publicInterface = ['destroy', 'refresh'];

    function HvstToggle($el, settings) {
        var defaults = {
                height: '30',
                seekerPadding: '10',
                activeClass: 'hvst-toggle-active',
                optionSlugField : 'slug'
            };

        this.settings = $.extend(defaults, settings);
        this.$el = $el;
        this.init();
    }

    HvstToggle.prototype.init = function() {
        this.hideInitialContainerList();
        this.buildToggleContainer();
        this.createToggleOptions();
    };

    HvstToggle.prototype.isInitialized = function() {
        return this.$el.data('hvstToggleInitialized');
    };

    HvstToggle.prototype.hideInitialContainerList = function() {
        this.$el.css({
            'position': 'absolute',
            'z-index': '-9999',
            'left': '-9999px'
        });
    };

    HvstToggle.prototype.buildToggleContainer = function() {
        var settings = this.settings,
            $el = this.$el,
            height = settings.height,
            lineHeight = settings.height - 10,
            seekerHeight = settings.height - 2,
            $optionsContainer;

        $el.after('
' +
        '
' +
        '

');

        $optionsContainer = this.getOptionsContainer();

        $optionsContainer.css({
            height : height,
            lineHeight : lineHeight + "px"
        });

        $optionsContainer.find('.hvst-seeker').css('height', seekerHeight);
    };

    HvstToggle.prototype.createToggleOptions = function() {
        var hvstToggle = this,
            $toggleOptions = this.$el.find('a'),
            $activeOption;

        this.getOptionsContainer().append($toggleOptions);
        $activeOption = this.getActiveOption();

        $toggleOptions
        .on('click', function() {
            hvstToggle.setActiveOption($(this));
        })
        .on('hover', function() {
            $(this).addClass('hvst-toggle-hover');
        })
        .on('mouseout mouseleave', function() {
            $(this).removeClass('hvst-toggle-hover');
        });

        if(!$activeOption.length) {
            this.setActiveOption($toggleOptions.eq(0));
        } else {
            this.moveSeekerTo($activeOption);
        }
    };

    HvstToggle.prototype.getActiveOption = function() {
        return this.getOptionsContainer()
        .find('a.' + this.settings.activeClass);
    };

    HvstToggle.prototype.setActiveOption = function($option) {
        var $optionsContainer = this.getOptionsContainer(),
            activeClass = this.settings.activeClass;

       $optionsContainer.find('.' + activeClass).removeClass(activeClass);
       $option.addClass(activeClass);

       this.moveSeekerTo($option);
    };

    HvstToggle.prototype.getOptionsContainer = function() {
        this.$optionsContainer = (this.$optionsContainer ||
            this.$el.next('.hvst-toggle').find('.hvst-toggle-bg'));

        return this.$optionsContainer;
    };

    HvstToggle.prototype.moveSeekerTo = function($option) {
        var seekerStyle = this.seekerStyle($option);
        this.getOptionsContainer().find('.hvst-seeker').css(seekerStyle);
    };

    HvstToggle.prototype.seekerStyle = function(obj) {
        var settings = this.settings,
            pos = $(obj).position(),
            left = pos.left - settings.seekerPadding - 11,
            // left = pos.left - ((settings.seekerPadding + 1) * 2),
            style = ($.browser.msie) ? {
                        'width': $(obj).outerWidth() + 'px',
                        'left': left + 11
                    } : {
                        'width': $(obj).outerWidth() + 'px',
                        '-webkit-transform': 'translate3d(' + left + 'px,0,0)',
                        '-moz-transform': 'translate3d(' + left + 'px,0,0)',
                        'transform': 'translate3d(' + left + 'px,0,0)'
                    };
        return style;
    };

    HvstToggle.prototype.refresh = function() {
        this.moveSeekerTo(this.getActiveOption());
    };

    HvstToggle.prototype.destroy = function() {
        this.$el.data('hvstToggle', null);
        this.getOptionsContainer().find('a').off();
    };

    $.fn.hvstToggle = function(method, settings) {
        // Allow chaining and to handle multiple
        return this.each(function() {
            var $this = $(this),
                hvstToggle = $this.data('hvstToggle');

            if(!hvstToggle) {
                hvstToggle = new HvstToggle($(this), settings);
            }

            if($.inArray(method, publicInterface) > -1) {
                hvstToggle[method]();
            }
        });
    };
}(jQuery));
<commit_msg>replaced browser detection by feature detection so we can use jQuery 1.10+<commit_after>(function($) {
    var publicInterface = ['destroy', 'refresh'],
        Utils;

    Utils = {
        supportsTransforms : function() {
            var $el = $('
'),
                transforms = [
                    '-webkit-transform',
                    '-o-transform',
                    '-ms-transform',
                    '-moz-transform',
                    'transform'
                ],
                translate = 'translated3d(1px,1px,1px)',
                hasTransforms;

            $el.appendTo($('body'));

            $.each(transforms, function(index, transform) {
                if($el.css(transform) !== null) {
                    $el.css(transform, translate);
                    hasTransforms = $el.css(transform) !== 'none';
                }
            });

            return hasTransforms;
        }
    };

    function HvstToggle($el, settings) {
        var defaults = {
                height: '30',
                seekerPadding: '10',
                activeClass: 'hvst-toggle-active',
                optionSlugField : 'slug'
            };

        this.settings = $.extend(defaults, settings);
        this.$el = $el;
        this.init();
    }

    HvstToggle.prototype.init = function() {
        this.hideInitialContainerList();
        this.buildToggleContainer();
        this.createToggleOptions();
    };

    HvstToggle.prototype.isInitialized = function() {
        return this.$el.data('hvstToggleInitialized');
    };

    HvstToggle.prototype.hideInitialContainerList = function() {
        this.$el.css({
            'position': 'absolute',
            'z-index': '-9999',
            'left': '-9999px'
        });
    };

    HvstToggle.prototype.buildToggleContainer = function() {
        var settings = this.settings,
            $el = this.$el,
            height = settings.height,
            lineHeight = settings.height - 10,
            seekerHeight = settings.height - 2,
            $optionsContainer;

        $el.after('
' +
        '
' +
        '

');

        $optionsContainer = this.getOptionsContainer();

        $optionsContainer.css({
            height : height,
            lineHeight : lineHeight + "px"
        });

        $optionsContainer.find('.hvst-seeker').css('height', seekerHeight);
    };

    HvstToggle.prototype.createToggleOptions = function() {
        var hvstToggle = this,
            $toggleOptions = this.$el.find('a'),
            $activeOption;

        this.getOptionsContainer().append($toggleOptions);
        $activeOption = this.getActiveOption();

        $toggleOptions
        .on('click', function() {
            hvstToggle.setActiveOption($(this));
        })
        .on('hover', function() {
            $(this).addClass('hvst-toggle-hover');
        })
        .on('mouseout mouseleave', function() {
            $(this).removeClass('hvst-toggle-hover');
        });

        if(!$activeOption.length) {
            this.setActiveOption($toggleOptions.eq(0));
        } else {
            this.moveSeekerTo($activeOption);
        }
    };

    HvstToggle.prototype.getActiveOption = function() {
        return this.getOptionsContainer()
        .find('a.' + this.settings.activeClass);
    };

    HvstToggle.prototype.setActiveOption = function($option) {
        var $optionsContainer = this.getOptionsContainer(),
            activeClass = this.settings.activeClass;

       $optionsContainer.find('.' + activeClass).removeClass(activeClass);
       $option.addClass(activeClass);

       this.moveSeekerTo($option);
    };

    HvstToggle.prototype.getOptionsContainer = function() {
        this.$optionsContainer = (this.$optionsContainer ||
            this.$el.next('.hvst-toggle').find('.hvst-toggle-bg'));

        return this.$optionsContainer;
    };

    HvstToggle.prototype.moveSeekerTo = function($option) {
        var seekerStyle = this.seekerStyle($option);
        this.getOptionsContainer().find('.hvst-seeker').css(seekerStyle);
    };

    HvstToggle.prototype.seekerStyle = function(obj) {
        var settings = this.settings,
            pos = $(obj).position(),
            left = pos.left - settings.seekerPadding - 11,
            // left = pos.left - ((settings.seekerPadding + 1) * 2),
            style = (Utils.supportsTransforms()) ? {
                        'width': $(obj).outerWidth() + 'px',
                        'left': left + 11
                    } : {
                        'width': $(obj).outerWidth() + 'px',
                        '-webkit-transform': 'translate3d(' + left + 'px,0,0)',
                        '-moz-transform': 'translate3d(' + left + 'px,0,0)',
                        'transform': 'translate3d(' + left + 'px,0,0)'
                    };
        return style;
    };

    HvstToggle.prototype.refresh = function() {
        this.moveSeekerTo(this.getActiveOption());
    };

    HvstToggle.prototype.destroy = function() {
        this.$el.data('hvstToggle', null);
        this.getOptionsContainer().find('a').off();
    };

    $.fn.hvstToggle = function(method, settings) {
        // Allow chaining and to handle multiple
        return this.each(function() {
            var $this = $(this),
                hvstToggle = $this.data('hvstToggle');

            if(!hvstToggle) {
                hvstToggle = new HvstToggle($this, settings);
                $this.data('hvstToggle', hvstToggle);
            }

            if($.inArray(method, publicInterface) > -1) {
                hvstToggle[method]();
            }
        });
    };
}(jQuery));
<|endoftext|>


<commit_before>// DISCLAIMER: I have no idea what I'm doing 🐶

const shl_url = "https://www.shl.se";
const ha_url = "https://www.hockeyallsvenskan.se";

let league_dict = {};
league_dict["SHL"] = { "url": "https://www.shl.se" };
league_dict["HA"] = { "url": "https://www.hockeyallsvenskan.se" };

const cheerio = require("cheerio");
const got = require('got');

const average = async () => {
    const dict = {};
    const array = [];
    let output = "";
    let response;

    for (let leauge in league_dict){
        output += `${leauge}:\n`
        url = league_dict[leauge]["url"];

    try {
        response = await got(url);
    } catch (error) {
        console.log(error);
    }

    const $ = cheerio.load(response.body, {
        decodeEntities: false,
    } );
    const teamsTable = $( '.rmss_t-team-statistics' );

    teamsTable.find("tr").each( ( index, element ) => {
        const $element = $( element );
        const name = $element.find( '.rmss_t-team-statistics__name-long' ).first().text();
        if (name == ''){
            return;
        }

        const rank = $element.find( '.rmss_t-styled__team-rank' ).first().text();
        const stats = $element.find( '.rmss_t-team-statistics__data').map(function() {
            return $(this).text();
        }).toArray();
        const games_played = stats[0];
        const plusminus = stats[1];
        const points = stats[2];
        const average = (points / games_played).toFixed(2);
        const dict_local = {};
        dict_local[name] = {
            "rank": rank,
            "games_played": games_played,
            "plusminus": plusminus,
            "points": points,
            "average": average
        };
        dict[name] = {
            "rank": rank,
            "games_played": games_played,
            "plusminus": plusminus,
            "points": points,
            "average": average
        };
        array.push(dict_local);
    });
    console.log("fax");
    const sorted = array
        .map(x => Object.entries(x)[0])
        .sort((a, b) => b[1].average - a[1].average)
        .map(x => x[0]);

    let count = 1;

    for (let i in sorted) {
        output += `${count} ${sorted[i]} ${dict[sorted[i]]["points"]} poäng på ${dict[sorted[i]]["games_played"]} matcher - snitt ${dict[sorted[i]]["average"]} (${dict[sorted[i]]["rank"]})\n`;
        count++;
    }

};
    return output;
};

module.exports = average;
<commit_msg>Don't include SHL in HA sorting and output<commit_after>// DISCLAIMER: I have no idea what I'm doing 🐶

const shl_url = "https://www.shl.se";
const ha_url = "https://www.hockeyallsvenskan.se";

let league_dict = {};
league_dict["SHL"] = { "url": "https://www.shl.se" };
league_dict["HA"] = { "url": "https://www.hockeyallsvenskan.se" };

const cheerio = require("cheerio");
const got = require('got');

const average = async () => {
    let output = "";
    let response;

    for (let leauge in league_dict){
        output += `${leauge}:\n`
        url = league_dict[leauge]["url"];
        const dict = {};
        const array = [];

    try {
        response = await got(url);
    } catch (error) {
        console.log(error);
    }

    const $ = cheerio.load(response.body, {
        decodeEntities: false,
    } );
    const teamsTable = $( '.rmss_t-team-statistics' );

    teamsTable.find("tr").each( ( index, element ) => {
        const $element = $( element );
        const name = $element.find( '.rmss_t-team-statistics__name-long' ).first().text();
        if (name == ''){
            return;
        }

        const rank = $element.find( '.rmss_t-styled__team-rank' ).first().text();
        const stats = $element.find( '.rmss_t-team-statistics__data').map(function() {
            return $(this).text();
        }).toArray();
        const games_played = stats[0];
        const plusminus = stats[1];
        const points = stats[2];
        const average = (points / games_played).toFixed(2);
        const dict_local = {};
        dict_local[name] = {
            "rank": rank,
            "games_played": games_played,
            "plusminus": plusminus,
            "points": points,
            "average": average
        };
        dict[name] = {
            "rank": rank,
            "games_played": games_played,
            "plusminus": plusminus,
            "points": points,
            "average": average
        };
        array.push(dict_local);
    });
    console.log("fax");
    const sorted = array
        .map(x => Object.entries(x)[0])
        .sort((a, b) => b[1].average - a[1].average)
        .map(x => x[0]);

    let count = 1;

    for (let i in sorted) {
        output += `${count} ${sorted[i]} ${dict[sorted[i]]["points"]} poäng på ${dict[sorted[i]]["games_played"]} matcher - snitt ${dict[sorted[i]]["average"]} (${dict[sorted[i]]["rank"]})\n`;
        count++;
    }

};
    return output;
};

module.exports = average;
<|endoftext|>


<commit_before>'use strict';

/**
 * Represents a queue.
 * @constructor
 * @param {number} maximum
 * @param {function(T, function(Error))} handler
 * @param {function(Error)=} done
 * @template T
 */
function Queue(maximum, handler, done) {
  this._current = 0;
  this._done = done;
  this._handler = handler;
  this._maximum = maximum < 1 ? 1 : maximum;
  this._queue = [];
}

/**
 * Kills the queue.
 * @param {Error} err
 */
Queue.prototype.kill = function(err) {
  this._queue = [];
  if (this._done) this._done(err);
};

/**
 * Push tasks on the queue.
 * @param {!(T|Array.<T>)} tasks
 */
Queue.prototype.push = function(tasks) {
  var that = this;
  [].concat(tasks).forEach(function(task) {
    that._queue.push(task);
  });
  while (that._queue.length && this._current < this._maximum) tryRun(that);
};

/**
 * Attempt to run an item from the queue.
 * @param {!Queue} that
 */
function tryRun(that) {
  if (that._current >= that._maximum || !that._queue.length) return;
  that._current += 1;
  that._handler(that._queue.shift(), function(err) {
    that._current -= 1;
    if (err || that._current === 0) return that.kill(err);
    tryRun(that);
  });
}

module.exports = Queue;
<commit_msg>Fixed incorrect queue empty.<commit_after>'use strict';

/**
 * Represents a queue.
 * @constructor
 * @param {number} maximum
 * @param {function(T, function(Error))} handler
 * @param {function(Error)=} done
 * @template T
 */
function Queue(maximum, handler, done) {
  this._current = 0;
  this._done = done;
  this._handler = handler;
  this._maximum = maximum < 1 ? 1 : maximum;
  this._queue = [];
}

/**
 * Kills the queue.
 * @param {Error} err
 */
Queue.prototype.kill = function(err) {
  this._queue = [];
  if (this._done) this._done(err);
};

/**
 * Push tasks on the queue.
 * @param {!(T|Array.<T>)} tasks
 */
Queue.prototype.push = function(tasks) {
  var that = this;
  [].concat(tasks).forEach(function(task) {
    that._queue.push(task);
  });
  while (that._queue.length && this._current < this._maximum) tryRun(that);
};

/**
 * Attempt to run an item from the queue.
 * @param {!Queue} that
 */
function tryRun(that) {
  if (that._current >= that._maximum || !that._queue.length) return;
  that._current += 1;
  that._handler(that._queue.shift(), function(err) {
    that._current -= 1;
    if (err) return that.kill(err);
    if (that._current === 0 && !that._queue.length) return that.kill();
    tryRun(that);
  });
}

module.exports = Queue;
<|endoftext|>


<commit_before>'use strict'

const stream = require('stream')
const debounce = require('debounce')

const DEBOUNCE_DURATION = 500

/**
 * Debounce the stream and outputs the last chunk.
 * @param {object} options The options
 * @param {number} duration The duration to debounce
 */
const debouncer = options => {
  options = options || {}

  let lastChunk = null
  let lastcb = null

  const duration = options.duration || DEBOUNCE_DURATION

  const resolveChunk = () => {
    if (lastcb) {
      lastcb(null, lastChunk)
    }
  }

  const resolveChunkDebounce = duration < Infinity ? debounce(resolveChunk, duration) : () => {}

  const debouncer = stream.Transform({
    objectMode: options.objectMode,

    transform (chunk, enc, cb) {
      lastChunk = chunk
      lastcb = cb

      resolveChunkDebounce()
    }
  })

  const write = debouncer.write

  debouncer.write = function () {
    if (lastcb) {
      lastcb()
      lastcb = null
    }
    return write.apply(this, arguments)
  }

  const end = debouncer.end

  debouncer.end = function () {
    if (lastcb) {
      lastcb(null, lastChunk)
      lastcb = null
    }
    return end.apply(this, arguments)
  }

  return debouncer
}

module.exports = debouncer
module.exports.obj = options => debouncer(Object.assign({objectMode: true}, options))
<commit_msg>refactor(stream-debouncer): refactor<commit_after>'use strict'

const stream = require('stream')
const debounce = require('debounce')

const DEBOUNCE_DURATION = 500

/**
 * Debounce the stream and outputs the last chunk.
 * @param {object} options The options
 * @param {number} duration The duration to debounce
 */
const debouncer = options => {
  options = options || {}

  let lastChunk = null
  let lastcb = null

  const duration = options.duration || DEBOUNCE_DURATION

  const resolveChunk = () => {
    if (lastcb) {
      lastcb(null, lastChunk)
    }
  }

  const resolveChunkDebounce = duration < Infinity ? debounce(resolveChunk, duration) : () => {}

  const debouncer = stream.Transform({
    objectMode: options.objectMode,

    transform (chunk, enc, cb) {
      lastChunk = chunk
      lastcb = cb

      resolveChunkDebounce()
    }
  })

  debouncer.write = function () {
    if (lastcb) {
      lastcb()
      lastcb = null
    }
    return stream.Transform.prototype.write.apply(this, arguments)
  }

  debouncer.end = function () {
    if (lastcb) {
      lastcb(null, lastChunk)
      lastcb = null
    }
    return stream.Transform.prototype.end.apply(this, arguments)
  }

  return debouncer
}

module.exports = debouncer
module.exports.obj = options => debouncer(Object.assign({objectMode: true}, options))
<|endoftext|>


<commit_before>'use strict'
var Clf = require('common-log-format')
var f = require('function-tools')
var view = require('./view')
var stats = require('./stats')
var JSONStream = require('JSONStream')
var streamVia = require('stream-via')

module.exports = streamLogStats

function streamLogStats (options) {
  var throttledRender = f.throttle(view.render, { restPeriod: options.refreshRate || 500 })

  function renderLogObject (logObject) {
    var requestSplit = logObject.request.split(' ')
    stats.addBytes(logObject.bytes)
    stats.requests++
    stats.addClient(logObject.remoteHost)
    var resourceLine = decodeURI(requestSplit[1])
    var availableSpace = process.stdout.columns - 36
    if (resourceLine.length > availableSpace) {
      stats.addResource(logObject.status + ' ' + requestSplit[0] + ' ...' + resourceLine.substr(-(availableSpace)), logObject.bytes)
    } else {
      stats.addResource(logObject.status + ' ' + requestSplit[0] + ' ' + resourceLine, logObject.bytes)
    }

    throttledRender(stats)
  }

  var clf = new Clf(options)
  clf
    .pipe(JSONStream.parse())
    .pipe(streamVia(renderLogObject, { objectMode: true }))
    .resume()
  return clf
}
<commit_msg>windows fix<commit_after>'use strict'
var Clf = require('common-log-format')
var f = require('function-tools')
var view = require('./view')
var stats = require('./stats')
var JSONStream = require('JSONStream')
var streamVia = require('stream-via')

module.exports = streamLogStats

function streamLogStats (options) {
  var throttledRender = f.throttle(view.render, { restPeriod: options.refreshRate || 500 })

  function renderLogObject (logObject) {
    var requestSplit = logObject.request.split(' ')
    stats.addBytes(logObject.bytes)
    stats.requests++
    stats.addClient(logObject.remoteHost)
    var resourceLine = decodeURI(requestSplit[1])
    var availableSpace = process.stdout.columns - 37
    if (resourceLine.length > availableSpace) {
      stats.addResource(logObject.status + ' ' + requestSplit[0] + ' ...' + resourceLine.substr(-(availableSpace)), logObject.bytes)
    } else {
      stats.addResource(logObject.status + ' ' + requestSplit[0] + ' ' + resourceLine, logObject.bytes)
    }

    throttledRender(stats)
  }

  var clf = new Clf(options)
  clf
    .pipe(JSONStream.parse())
    .pipe(streamVia(renderLogObject, { objectMode: true }))
    .resume()
  return clf
}
<|endoftext|>


<commit_before><commit_msg>Update tests to pass with a default token<commit_after><|endoftext|>


<commit_before>#!/usr/bin/env node

var url = require('url');
var http = require('http');
var path = require('path');
var async = require('async');
var redis = require('redis');
var _ = require('underscore');
var cheerio = require('cheerio');
var express = require('express');
var Twitter = require('twitter');
var request = require('request');
var sockio = require('socket.io');

/*
 * The main web server function. This will create a webserver
 * and then listen to Twitter for new tweets with URLs.
 */

function main(track) {
  var app = express();
  var server = http.Server(app);
  var io = sockio(server);
  var db = getRedis();

  app.use(express.static('public'));
  app.enable('trust proxy');
  app.set('views', path.join(__dirname, 'views'));
  app.set('view engine', 'hbs');
  app.set('json spaces', 2);

  app.get('/', function(req, res) {
    res.render('index', {track: track});
  });

  app.get('/stats.json', function(req, res) {
    getStats(db, function (stats) {
      res.set('cache-control', 'public, max-age: 300');
      res.json(stats);
    });
  });

  listenForTweets(track, db, io);
  server.listen(process.env.PORT || 3000);
}

/*
 * get a redis connection
 */

function getRedis(redisUrl) {
  var rtg = redisUrl || process.env.REDISTOGO_URL || "redis://localhost:6379";
  var uri = url.parse(rtg);
  var db = redis.createClient(uri.port, uri.hostname);
  if (uri.auth) {
    console.log('auth:', uri.auth);
    var auth = uri.auth.split(':').pop();
    db.auth(auth);
  }
  return db;
}

/*
 * Listen for tweets to add to the database.
 */

function listenForTweets(track, db, io) {
  var e = process.env;
  if (!(e.TWITTER_CONSUMER_KEY && e.TWITTER_CONSUMER_SECRET && e.TWITTER_ACCESS_TOKEN && e.TWITTER_ACCESS_TOKEN_SECRET)) {
    console.error("you must set TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, TWITTER_ACCESS_TOKEN and TWITTER_ACCESS_TOKEN_SECRET in your environment");
    process.exit(1)
  }
  var twtr = new Twitter({
    consumer_key: e.TWITTER_CONSUMER_KEY,
    consumer_secret: e.TWITTER_CONSUMER_SECRET,
    access_token_key: e.TWITTER_ACCESS_TOKEN,
    access_token_secret: e.TWITTER_ACCESS_TOKEN_SECRET
  });
  var stats = new Stats(db, io);

  console.log('connecting to twitter filter stream for', track);
  twtr.stream('statuses/filter', {track: track}, function(stream) {
    // kind of awkward that we can't just say status.checkTweet here
    // but we need to make sure the contet is correct
    //stream.on('data', Stats.prototype.checkTweet.bind(stats));
    stream.on('data', function(tweet) {
      stats.checkTweet(tweet);
    });
    stream.on('error', function(error) {
      console.log('twitter problem:', error);
    });
  });
}

/*
 * a class to extract stats from Tweets
 */

function Stats(db, io) {
  var that = this;
  var lookupUrl = function(job, done) {
    console.log('looking up url: ' + job.url);
    var opts = {
      url: job.url,
      headers: {
        'User-Agent': 'Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0'
      }
    };
    request(opts, function (error, response, body) {
      if (! error) {
        var mimetype = response.headers['content-type'];
        var title = response.request.uri.href;
        if (mimetype && mimetype.match(/html/)) {
          var $ = cheerio.load(body);
          title = $("head meta[property='og:title']").attr('content');
          if (! title) {
            title = $("head title").text();
          }
        }
        var r = {
          url: response.request.uri.href,
          title: title,
          tweet: job.tweet
        };
        io.sockets.emit('update', r);
        that.addResource(r);
        done();
        $ = null;
      } else {
        done(error);
      }
    });
  };
  this.db = db;
  this.queue = async.queue(lookupUrl, 2);
}

Stats.prototype.checkTweet = function(tweet) {
  var that = this;
  console.log('processing tweet: ' + tweet.id_str);
  _.each(tweet.entities.urls, function(urlEntity) {
    var url = urlEntity.expanded_url;
    console.log('queueing lookup for ' + url);
    that.queue.push({url: url, tweet: tweet}, function (err) {
      if (! err) {
        console.log("finished processing url: " + url);
      } else {
        console.log("error while processing url: " + err);
      }
    });
  });
};

Stats.prototype.addResource = function(r) {
  var tweetId = 'tweet:' + r.tweet.id_str;
  var avatar = r.tweet.user.profile_image_url;
  var name = r.tweet.user.screen_name;
  var tweetUrl = "https://twitter.com/" + r.tweet.user.screen_name + "/statuses/" + r.tweet.id_str;

  console.log("tallying: ", r.url, r.title, name, tweetUrl);

  this.db.hset(r.url, "title", r.title);
  this.db.zincrby('urls', 1, r.url);
  this.db.lpush('tweets:' + r.url, tweetId);
  this.db.hset(tweetId, "url", tweetUrl);
  this.db.hset(tweetId, "name", name);
  this.db.hset(tweetId, "avatar", avatar);
};

/*
 * a function to get a big chunk of statistics back from redis
 */

function getStats(db, callback) {

  var addTitle = function(score, callback) {
    db.hget(score.url, 'title', function(err, result) {
      score.title = result;
      callback(null, score);
    });
  };

  var addTweets = function(score, callback) {
    db.lrange('tweets:' + score.url, 0, -1, function (err, tweets) {
      async.mapSeries(tweets, addTweetInfo, function (err, results) {
        score.tweets = results;
        callback(null, score);
      });
    });
  };

  var addTweetInfo = function(tweetId, callback) {
    db.hgetall(tweetId, function (err, tweet) {
      callback(null, tweet);
    });
  };

  db.zrevrange('urls', 0, 200, 'withscores', function(err, results) {
    scores = [];
    for (var i = 0; i < results.length; i+=2) {
      scores.push({
        url: results[i],
        count: results[i + 1]
      });
    }
    async.mapSeries(scores, addTitle, function(err, results) {
      async.mapSeries(scores, addTweets, function(err, results) {
        callback(results);
      });
    });
  });

}

/*
 * when run from command line
 */

if (require.main === module) {
  var track = process.argv[2] || process.env.EARLS_TRACK;
  if (! track) {
    console.log("please supply track either as argument or using EARLS_TRACK environment variable");
    process.exit(1);
  } else {
    main(track);
  }
}

exports.Stats = Stats;
exports.getRedis = getRedis;
<commit_msg>need this to be able to use load.js<commit_after>#!/usr/bin/env node

var url = require('url');
var http = require('http');
var path = require('path');
var async = require('async');
var redis = require('redis');
var _ = require('underscore');
var cheerio = require('cheerio');
var express = require('express');
var Twitter = require('twitter');
var request = require('request');
var sockio = require('socket.io');

/*
 * The main web server function. This will create a webserver
 * and then listen to Twitter for new tweets with URLs.
 */

function main(track) {
  var app = express();
  var server = http.Server(app);
  var io = sockio(server);
  var db = getRedis();

  app.use(express.static('public'));
  app.enable('trust proxy');
  app.set('views', path.join(__dirname, 'views'));
  app.set('view engine', 'hbs');
  app.set('json spaces', 2);

  app.get('/', function(req, res) {
    res.render('index', {track: track});
  });

  app.get('/stats.json', function(req, res) {
    getStats(db, function (stats) {
      res.set('cache-control', 'public, max-age: 300');
      res.json(stats);
    });
  });

  listenForTweets(track, db, io);
  server.listen(process.env.PORT || 3000);
}

/*
 * get a redis connection
 */

function getRedis(redisUrl) {
  var rtg = redisUrl || process.env.REDISTOGO_URL || "redis://localhost:6379";
  var uri = url.parse(rtg);
  var db = redis.createClient(uri.port, uri.hostname);
  if (uri.auth) {
    console.log('auth:', uri.auth);
    var auth = uri.auth.split(':').pop();
    db.auth(auth);
  }
  return db;
}

/*
 * Listen for tweets to add to the database.
 */

function listenForTweets(track, db, io) {
  var e = process.env;
  if (!(e.TWITTER_CONSUMER_KEY && e.TWITTER_CONSUMER_SECRET && e.TWITTER_ACCESS_TOKEN && e.TWITTER_ACCESS_TOKEN_SECRET)) {
    console.error("you must set TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, TWITTER_ACCESS_TOKEN and TWITTER_ACCESS_TOKEN_SECRET in your environment");
    process.exit(1)
  }
  var twtr = new Twitter({
    consumer_key: e.TWITTER_CONSUMER_KEY,
    consumer_secret: e.TWITTER_CONSUMER_SECRET,
    access_token_key: e.TWITTER_ACCESS_TOKEN,
    access_token_secret: e.TWITTER_ACCESS_TOKEN_SECRET
  });
  var stats = new Stats(db, io);

  console.log('connecting to twitter filter stream for', track);
  twtr.stream('statuses/filter', {track: track}, function(stream) {
    // kind of awkward that we can't just say status.checkTweet here
    // but we need to make sure the contet is correct
    //stream.on('data', Stats.prototype.checkTweet.bind(stats));
    stream.on('data', function(tweet) {
      stats.checkTweet(tweet);
    });
    stream.on('error', function(error) {
      console.log('twitter problem:', error);
    });
  });
}

/*
 * a class to extract stats from Tweets
 */

function Stats(db, io) {
  var that = this;
  var lookupUrl = function(job, done) {
    console.log('looking up url: ' + job.url);
    var opts = {
      url: job.url,
      headers: {
        'User-Agent': 'Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0'
      }
    };
    request(opts, function (error, response, body) {
      if (! error) {
        var mimetype = response.headers['content-type'];
        var title = response.request.uri.href;
        if (mimetype && mimetype.match(/html/)) {
          var $ = cheerio.load(body);
          title = $("head meta[property='og:title']").attr('content');
          if (! title) {
            title = $("head title").text();
          }
        }
        var r = {
          url: response.request.uri.href,
          title: title,
          tweet: job.tweet
        };
        if (typeof(io) != 'undefined') {
          io.sockets.emit('update', r);
        }
        that.addResource(r);
        done();
        $ = null;
      } else {
        done(error);
      }
    });
  };
  this.db = db;
  this.queue = async.queue(lookupUrl, 2);
}

Stats.prototype.checkTweet = function(tweet) {
  var that = this;
  console.log('processing tweet: ' + tweet.id_str);
  _.each(tweet.entities.urls, function(urlEntity) {
    var url = urlEntity.expanded_url;
    console.log('queueing lookup for ' + url);
    that.queue.push({url: url, tweet: tweet}, function (err) {
      if (! err) {
        console.log("finished processing url: " + url);
      } else {
        console.log("error while processing url: " + err);
      }
    });
  });
};

Stats.prototype.addResource = function(r) {
  var tweetId = 'tweet:' + r.tweet.id_str;
  var avatar = r.tweet.user.profile_image_url;
  var name = r.tweet.user.screen_name;
  var tweetUrl = "https://twitter.com/" + r.tweet.user.screen_name + "/statuses/" + r.tweet.id_str;

  console.log("tallying: ", r.url, r.title, name, tweetUrl);

  this.db.hset(r.url, "title", r.title);
  this.db.zincrby('urls', 1, r.url);
  this.db.lpush('tweets:' + r.url, tweetId);
  this.db.hset(tweetId, "url", tweetUrl);
  this.db.hset(tweetId, "name", name);
  this.db.hset(tweetId, "avatar", avatar);
};

/*
 * a function to get a big chunk of statistics back from redis
 */

function getStats(db, callback) {

  var addTitle = function(score, callback) {
    db.hget(score.url, 'title', function(err, result) {
      score.title = result;
      callback(null, score);
    });
  };

  var addTweets = function(score, callback) {
    db.lrange('tweets:' + score.url, 0, -1, function (err, tweets) {
      async.mapSeries(tweets, addTweetInfo, function (err, results) {
        score.tweets = results;
        callback(null, score);
      });
    });
  };

  var addTweetInfo = function(tweetId, callback) {
    db.hgetall(tweetId, function (err, tweet) {
      callback(null, tweet);
    });
  };

  db.zrevrange('urls', 0, 200, 'withscores', function(err, results) {
    scores = [];
    for (var i = 0; i < results.length; i+=2) {
      scores.push({
        url: results[i],
        count: results[i + 1]
      });
    }
    async.mapSeries(scores, addTitle, function(err, results) {
      async.mapSeries(scores, addTweets, function(err, results) {
        callback(results);
      });
    });
  });

}

/*
 * when run from command line
 */

if (require.main === module) {
  var track = process.argv[2] || process.env.EARLS_TRACK;
  if (! track) {
    console.log("please supply track either as argument or using EARLS_TRACK environment variable");
    process.exit(1);
  } else {
    main(track);
  }
}

exports.Stats = Stats;
exports.getRedis = getRedis;
<|endoftext|>


<commit_before>import Window from './window'
import {ipcMain} from 'electron'
import {resolveRoot} from '../resolve-root'
import hotPath from '../hot-path'
import {windowStyle} from '../shared/styles/style-guide'
import {forceMainWindowPosition} from '../shared/local-debug.desktop'
import AppState from './app-state'
import getenv from 'getenv'
import {hideDockIcon} from './dock-icon'

export default function () {
  let appState = new AppState({
    defaultWidth: windowStyle.width,
    defaultHeight: windowStyle.height,
  })

  const mainWindow = new Window(
    resolveRoot('renderer', `index.html?src=${hotPath('index.bundle.js')}`), {
      x: appState.state.x,
      y: appState.state.y,
      width: appState.state.width,
      height: appState.state.height,
      minWidth: windowStyle.minWidth,
      minHeight: windowStyle.minHeight,
      show: false,
    }
  )

  appState.manageWindow(mainWindow.window)

  if (__DEV__ && forceMainWindowPosition) {
    mainWindow.window.setPosition(forceMainWindowPosition.x, forceMainWindowPosition.y)
  }

  const isRestore = getenv.boolish('KEYBASE_RESTORE_UI', false)
  const startUI = getenv.string('KEYBASE_START_UI', '')

  // We show the main window on startup if:
  //  - We are not restoring the UI (after update, or boot)
  //    Or, we are restoring UI and the window was previously visible (in app state)
  //  - And, startUI is not set to 'hideWindow'.
  const showMainWindow = (!isRestore || (isRestore && !appState.state.windowHidden)) && (startUI !== 'hideWindow')

  if (showMainWindow) {
    // On Windows we can try showing before Windows is ready
    // This will result in a dropped .show request
    // We add a listener to `did-finish-load` so we can show it when
    // Windows is ready.
    mainWindow.show(true)
    mainWindow.window.webContents.once('did-finish-load', () => {
      mainWindow.show(true)
    })
  }

  if (isRestore && appState.state.dockHidden) {
    hideDockIcon()
  }

  ipcMain.on('showMain', () => {
    mainWindow.show(true)
  })

  ipcMain.on('tabChanged', (event, tab) => {
    appState.state.tab = tab
    appState.saveState()
  })

  return mainWindow
}
<commit_msg>Logging for window restore (#3762)<commit_after>import Window from './window'
import {ipcMain} from 'electron'
import {resolveRoot} from '../resolve-root'
import hotPath from '../hot-path'
import {windowStyle} from '../shared/styles/style-guide'
import {forceMainWindowPosition} from '../shared/local-debug.desktop'
import AppState from './app-state'
import getenv from 'getenv'
import {hideDockIcon} from './dock-icon'

export default function () {
  let appState = new AppState({
    defaultWidth: windowStyle.width,
    defaultHeight: windowStyle.height,
  })

  const mainWindow = new Window(
    resolveRoot('renderer', `index.html?src=${hotPath('index.bundle.js')}`), {
      x: appState.state.x,
      y: appState.state.y,
      width: appState.state.width,
      height: appState.state.height,
      minWidth: windowStyle.minWidth,
      minHeight: windowStyle.minHeight,
      show: false,
    }
  )

  appState.manageWindow(mainWindow.window)

  if (__DEV__ && forceMainWindowPosition) {
    mainWindow.window.setPosition(forceMainWindowPosition.x, forceMainWindowPosition.y)
  }

  const isRestore = getenv.boolish('KEYBASE_RESTORE_UI', false)
  const startUI = getenv.string('KEYBASE_START_UI', '')
  console.log('Main window, isRestore:', isRestore, 'startUI:', startUI)

  // We show the main window on startup if:
  //  - We are not restoring the UI (after update, or boot)
  //    Or, we are restoring UI and the window was previously visible (in app state)
  //  - And, startUI is not set to 'hideWindow'.
  const showMainWindow = (!isRestore || (isRestore && !appState.state.windowHidden)) && (startUI !== 'hideWindow')

  if (showMainWindow) {
    // On Windows we can try showing before Windows is ready
    // This will result in a dropped .show request
    // We add a listener to `did-finish-load` so we can show it when
    // Windows is ready.
    mainWindow.show(true)
    mainWindow.window.webContents.once('did-finish-load', () => {
      mainWindow.show(true)
    })
  }

  if (isRestore && appState.state.dockHidden) {
    hideDockIcon()
  }

  ipcMain.on('showMain', () => {
    mainWindow.show(true)
  })

  ipcMain.on('tabChanged', (event, tab) => {
    appState.state.tab = tab
    appState.saveState()
  })

  return mainWindow
}
<|endoftext|>


<commit_before><commit_msg>Removing trailing commas<commit_after><|endoftext|>


<commit_before>"use strict";

require('es6-promise').polyfill();
require('isomorphic-fetch');
var baseUrl = require("./utils/config").baseUrl;
var parseUser = require("./parsers/user");
var parseTalk = require("./parsers/talk");

function normalisePath(path) {
    return (/^\//.test(path)) ? path : '/' + path;
}

function fetchPath(path) {
    return fetch(path).then(function(response) {
        if (response.status >= 400) {
            throw new Error("Bad response from server");
        }

        return response.text();
    }).catch(function(err) {
        console.log(err);
    });
}

function getUser(userId, page) {
    if(!userId) { throw new Error("User id needed to scrape"); }

    page = page || 1;
    var path = baseUrl + normalisePath(userId) + "?page=" + page;

    return fetchPath(path).then(function(data){
        return parseUser(data, page);
    });
}

function getTalk(talkId) {
    if(!talkId) { throw new Error("Talk id needed to scrape"); }

    var path = baseUrl + normalisePath(talkId);

    return fetchPath(path).then(function(data){
        return parseTalk(data);
    });
}

module.exports = {
    getTalk: getTalk,
    getUser: getUser
};
<commit_msg>Ensure page param is a Number<commit_after>"use strict";

require('es6-promise').polyfill();
require('isomorphic-fetch');
var baseUrl = require("./utils/config").baseUrl;
var parseUser = require("./parsers/user");
var parseTalk = require("./parsers/talk");

function normalisePath(path) {
    return (/^\//.test(path)) ? path : '/' + path;
}

function fetchPath(path) {
    return fetch(path).then(function(response) {
        if (response.status >= 400) {
            throw new Error("Bad response from server");
        }

        return response.text();
    }).catch(function(err) {
        console.log(err);
    });
}

function getUser(userId, page) {
    if(!userId) { throw new Error("User id needed to scrape"); }

    page = parseInt(page) || 1;
    var path = baseUrl + normalisePath(userId) + "?page=" + page;

    return fetchPath(path).then(function(data){
        return parseUser(data, page);
    });
}

function getTalk(talkId) {
    if(!talkId) { throw new Error("Talk id needed to scrape"); }

    var path = baseUrl + normalisePath(talkId);

    return fetchPath(path).then(function(data){
        return parseTalk(data);
    });
}

module.exports = {
    getTalk: getTalk,
    getUser: getUser
};
<|endoftext|>


<commit_before>
/**
element(name, options)

Registers a custom element and returns its constructor. This function aims to
render the API for creating custom elements a little more... sane.

- name: 'name'     Custom element tag name
- options: {
       extends:    Name of tag to extend to make the element a custom built-in
       mode:       'open' or 'closed', defaults to 'closed'
       template:   String or template node or id used to create a shadow DOM
       attributes: An object of handler functions for attribute changes
       properties: An object of property definitions for the element prototype
       construct:  Lifecycle handler called during element construction
       connect:    Lifecycle handler called when element added to DOM
       disconnect: Lifecycle handler called when element removed from DOM
       enable:     Lifecycle handler called when form element enabled
       disable:    Lifecycle handler called when form element disabled
       reset:      Lifecycle handler called when form element reset
       restore:    Lifecycle handler called when form element restored
  }

The `extends` property can only create customised built-in elements in browsers
that support the feature. Safari is a known culprit. Mileage will vary.

The effects of the `mode` option are subtle. In 'closed' mode, the element is
not given a publicly accessible `shadowRoot` property, and events that traverse
the shadow boundary are retargeted (as they are in 'open' mode) but also have
their `path` list truncated.

Where the `properties` object contains a definition for a `value` property work
is done to give the element form field behaviour. The constructor is assigned
the property `formAssociated` which signals to the browser that it constructs
form fields. Where they are not defined in `properties` the prototype is
assigned default handlers for the standard properties `type`, `name`, `form`,
`labels`, `validity`, `validationMessage`, `willValidate`, `checkValidity`
and `reportValidity`. Form behaviour is also mildly polyfilled in browsers
without support by inserting a hidden input inside the element but outside the
shadow DOM. Mileage will vary. Managing focus can be a problem.

At the start of initialisation the `construct` handler is called. Use it to
set up a shadow root and define event handlers. Children and attributes must
not be inspected or assigned at this point – doing so will throw in some cases,
eg. construction via `createElement()`.

Following that, attribute handlers are called for attributes declared in the
HTML. The parser normally calls these in source order. This can be problematic
if you require setup to run in a specific order. Here, they are run in the order
declared in the `attributes` object.

Finally the `connect` handler is called when the element is placed in the DOM
or if it is already in the DOM and is being upgraded. All lifecycle handlers
are called with the parameter `shadow`.
*/

import create from './create.js';

const DEBUG = true;

const assign = Object.assign;

const constructors = {
    'a':        HTMLAnchorElement,
    'p':        HTMLParagraphElement,
    'br':       HTMLBRElement,
    'img':      HTMLImageElement,
    'template': HTMLTemplateElement
};

const $internals = Symbol('internals');
const $shadow    = Symbol('shadow');

const formProperties = {
    // These properties echo those provided by native form controls.
    // They are not strictly required, but provided for consistency.
    type: { value: 'text' },

    name: {
        set: function(name) { return this.setAttribute('name', name); },
        get: function() { return this.getAttribute('name') || ''; }
    },

    form:              { get: function() { return this[$internals].form; }},
    labels:            { get: function() { return this[$internals].labels; }},
    validity:          { get: function() { return this[$internals].validity; }},
    validationMessage: { get: function() { return this[$internals].validationMessage; }},
    willValidate:      { get: function() { return this[$internals].willValidate; }},
    checkValidity:     { value: function() { return this[$internals].checkValidity(); }},
    reportValidity:    { value: function() { return this[$internals].reportValidity(); }}
};

function getElementConstructor(tag) {
        // Return a constructor from the known list of tag names – not all tags
        // have constructor names that match their tags
    return constructors[tag]
        // Or assemble the tag name in the form "HTMLTagElement" and return
        // that property of the window object
        || window['HTML' + tag[0].toUpperCase() + tag.slice(1) + 'Element']
        || (() => {
            throw new Error('Constructor not found for tag "' + tag + '"');
        })();
}

function getTemplateById(id) {
    const template = document.getElementById(id);

    if (!template || !template.content) {
        throw new Error('Template id="' + id + '" not found in document');
    }

    return template;
}

function getTemplate(template) {
    if (!template) { return; }

    return typeof template === 'string' ?
        // If template is an #id search for <template id="id">
        template[0] === '#' ? getTemplateById(template.slice(1)) :
        // It must be a string of HTML
        template :
    template.content ?
        // It must be a template node
        template :
        // Whatever it is, we don't support it
        function(){
            throw new Error('element() options.template not a template node, id or string');
        }() ;
}

function transferProperty(elem, key) {
    if (elem.hasOwnProperty(key)) {
        const value = elem[key];
        delete elem[key];
        elem[key] = value;
    }

    return elem;
}

function createShadow(template, elem, options) {
    if (!template) { return; }

    // Create a shadow root if there is DOM content. Shadows may be 'open' or
    // 'closed'. Closed shadows are not exposed via element.shadowRoot, and
    // events propagating from inside of them report the element as target.
    const shadow = elem.attachShadow({
        mode: options.mode || 'closed',
        delegatesFocus: true
    });

    elem[$shadow] = shadow;

    // If template is a <template>
    if (typeof template === 'string') {
        shadow.innerHTML = template;
    }
    else {
        shadow.appendChild(template.content.cloneNode(true));
    }

    return shadow;
}

function attachInternals(elem) {
    // Use native attachInternals where it exists
    if (elem.attachInternals) {
        return elem.attachInternals();
    }

    // Otherwise polyfill it with a pseudo internals object, actually a hidden
    // input that we put inside element (but outside the shadow DOM). We may
    // not yet put this in the DOM however – it violates the spec to give a
    // custom element children before it's contents are parsed. Instead we
    // wait until connectCallback.
    const hidden = create('input', { type: 'hidden', name: elem.name });

    // Polyfill internals object setFormValue
    hidden.setFormValue = function(value) {
        this.value = value;
    };

    return hidden;
}

function appendInternalsCallback() {
    // If we have simulated form internals, append the hidden input now
    if (this[$internals] && !this.attachInternals) {
        this.appendChild(this[$internals]);
    }
}

function primeAttributes(elem) {
    elem._initialAttributes = {};
    elem._n = 0;
}

function advanceAttributes(elem, attributes, handlers) {
    const values = elem._initialAttributes;

    while(elem._n < attributes.length && values[attributes[elem._n]] !== undefined) {
        //console.log('ADVANCE ATTR', attributes[elem._n]);
        handlers[attributes[elem._n]].call(elem, values[attributes[elem._n]]);
        ++elem._n;
    }
}

function flushAttributes(elem, attributes, handlers) {
    if (!elem._initialAttributes) { return; }

    const values = elem._initialAttributes;

    while(elem._n < attributes.length) {
        //console.log('FLUSH ATTR', attributes[elem._n]);
        if (values[attributes[elem._n]] !== undefined) {
            handlers[attributes[elem._n]].call(elem, values[attributes[elem._n]]);
        }
        ++elem._n;
    }

    delete elem._initialAttributes;
    delete elem._n;
}

export default function element(name, options) {
    // Get the element constructor from options.extends, or the
    // base HTMLElement constructor
    const constructor = options.extends ?
        getElementConstructor(options.extends) :
        HTMLElement ;

    // Get a template node or HTML string from options.template
    const template = getTemplate(options.template);

    function Element() {
        // Construct an instance from Constructor using the Element prototype
        const elem   = Reflect.construct(constructor, arguments, Element);
        const shadow = createShadow(template, elem, options);

        if (Element.formAssociated) {
            // Get access to the internal form control API
            elem[$internals] = attachInternals(elem);
        }

        options.construct && options.construct.call(elem, shadow);

        // Preserve initialisation order of attribute initialisation by
        // queueing them
        if (options.attributes) {
            primeAttributes(elem);

            // Wait a tick to flush attributes
            Promise.resolve(1).then(function() {
                flushAttributes(elem, Element.observedAttributes, options);
            });
        }

        // At this point, if properties have already been set before the
        // element was upgraded, they exist on the elem itself, where we have
        // just upgraded it's protytype to define those properties those
        // definitions will never be reached. Either:
        //
        // 1. Define properties on the instance instead of the prototype
        //    Object.defineProperties(elem, properties);
        //
        // 2. Take a great deal of care not to set properties before an element
        //    is upgraded. I can't impose a restriction like that.
        //
        // 3. Copy defined properties to their prototype handlers and delete
        //    them on the instance.
        //
        // Let's go with 3. I'm not happy you have to do this, though.
        options.properties
        && Object.keys(options.properties).reduce(transferProperty, elem);

        return elem;
    }

    // Properties
    //
    // Must be defined before attributeChangedCallback, but I cannot figure out
    // why. Where one of the properties is `value`, the element is set up as a
    // form element.

    if (options.properties && options.properties.value) {
        // Flag the Element class as formAssociated
        Element.formAssociated = true;

        Element.prototype = Object.create(constructor.prototype, assign({}, formProperties, options.properties, {
            value: {
                get: options.properties.value.get,
                set: function() {
                    // After setting value
                    options.properties.value.set.apply(this, arguments);

                    // Copy it to internal form state
                    this[$internals].setFormValue('' + this.value);
                }
            }
        })) ;
    }
    else {
        Element.prototype = Object.create(constructor.prototype, options.properties || {}) ;
    }

    // Attributes

    if (options.attributes) {
        Element.observedAttributes = Object.keys(options.attributes);

        Element.prototype.attributeChangedCallback = function(name, old, value) {
            if (!this._initialAttributes) {
                return options.attributes[name].call(this, value);
            }

            // Keep a record of attribute values to be applied in
            // observedAttributes order
            this._initialAttributes[name] = value;
            advanceAttributes(this, Element.observedAttributes, options.attributes);
        };
    }

    // Lifecycle

    Element.prototype.connectedCallback = function() {
        if (this._initialAttributes) {
            flushAttributes(this, Element.observedAttributes, options.attributes);
        }

        if (Element.formAssociated) {
            appendInternalsCallback.call(this);
        }

        if (options.connect) {
            options.connect.call(this, this[$shadow]);
        }

        if (DEBUG) { console.log('Connected to document:', this); }
    }

    if (options.disconnect) {
        Element.prototype.disconnectedCallback = function() {
            return options.disconnect.call(this, this[$shadow]);
        };
    }

    if (Element.formAssociated) {
        if (options.enable || options.disable) {
            Element.prototype.formDisabledCallback = function(disabled) {
                return disabled ?
                    options.disable && options.disable.call(this, this[$shadow]) :
                    options.enable && options.enable.call(this, this[$shadow]) ;
            };
        }

        if (options.reset) {
            Element.prototype.formResetCallback = function() {
                return options.reset.call(this, this[$shadow]);
            };
        }

        if (options.restore) {
            Element.prototype.formStateRestoreCallback = function() {
                return options.restore.call(this, this[$shadow]);
            };
        }
    }

    // Define element

    window.customElements.define(name, Element, options);
    return Element;
}
<commit_msg>Search for element template on first element construct<commit_after>
/**
element(name, options)

Registers a custom element and returns its constructor. This function aims to
render the API for creating custom elements a little more... sane.

- name: 'name'     Custom element tag name
- options: {
       extends:    Name of tag to extend to make the element a custom built-in
       mode:       'open' or 'closed', defaults to 'closed'
       template:   String or template node or id used to create a shadow DOM
       attributes: An object of handler functions for attribute changes
       properties: An object of property definitions for the element prototype
       construct:  Lifecycle handler called during element construction
       connect:    Lifecycle handler called when element added to DOM
       disconnect: Lifecycle handler called when element removed from DOM
       enable:     Lifecycle handler called when form element enabled
       disable:    Lifecycle handler called when form element disabled
       reset:      Lifecycle handler called when form element reset
       restore:    Lifecycle handler called when form element restored
  }

The `extends` property can only create customised built-in elements in browsers
that support the feature. Safari is a known culprit. Mileage will vary.

The effects of the `mode` option are subtle. In 'closed' mode, the element is
not given a publicly accessible `shadowRoot` property, and events that traverse
the shadow boundary are retargeted (as they are in 'open' mode) but also have
their `path` list truncated.

Where the `properties` object contains a definition for a `value` property work
is done to give the element form field behaviour. The constructor is assigned
the property `formAssociated` which signals to the browser that it constructs
form fields. Where they are not defined in `properties` the prototype is
assigned default handlers for the standard properties `type`, `name`, `form`,
`labels`, `validity`, `validationMessage`, `willValidate`, `checkValidity`
and `reportValidity`. Form behaviour is also mildly polyfilled in browsers
without support by inserting a hidden input inside the element but outside the
shadow DOM. Mileage will vary. Managing focus can be a problem.

At the start of initialisation the `construct` handler is called. Use it to
set up a shadow root and define event handlers. Children and attributes must
not be inspected or assigned at this point – doing so will throw in some cases,
eg. construction via `createElement()`.

Following that, attribute handlers are called for attributes declared in the
HTML. The parser normally calls these in source order. This can be problematic
if you require setup to run in a specific order. Here, they are run in the order
declared in the `attributes` object.

Finally the `connect` handler is called when the element is placed in the DOM
or if it is already in the DOM and is being upgraded. All lifecycle handlers
are called with the parameter `shadow`.
*/

import create from './create.js';

const DEBUG = true;

const assign = Object.assign;

const constructors = {
    'a':        HTMLAnchorElement,
    'p':        HTMLParagraphElement,
    'br':       HTMLBRElement,
    'img':      HTMLImageElement,
    'template': HTMLTemplateElement
};

const $internals = Symbol('internals');
const $shadow    = Symbol('shadow');

const formProperties = {
    // These properties echo those provided by native form controls.
    // They are not strictly required, but provided for consistency.
    type: { value: 'text' },

    name: {
        set: function(name) { return this.setAttribute('name', name); },
        get: function() { return this.getAttribute('name') || ''; }
    },

    form:              { get: function() { return this[$internals].form; }},
    labels:            { get: function() { return this[$internals].labels; }},
    validity:          { get: function() { return this[$internals].validity; }},
    validationMessage: { get: function() { return this[$internals].validationMessage; }},
    willValidate:      { get: function() { return this[$internals].willValidate; }},
    checkValidity:     { value: function() { return this[$internals].checkValidity(); }},
    reportValidity:    { value: function() { return this[$internals].reportValidity(); }}
};

function getElementConstructor(tag) {
        // Return a constructor from the known list of tag names – not all tags
        // have constructor names that match their tags
    return constructors[tag]
        // Or assemble the tag name in the form "HTMLTagElement" and return
        // that property of the window object
        || window['HTML' + tag[0].toUpperCase() + tag.slice(1) + 'Element']
        || (() => {
            throw new Error('Constructor not found for tag "' + tag + '"');
        })();
}

function getTemplateById(id) {
    const template = document.getElementById(id);

    if (!template || !template.content) {
        throw new Error('Template id="' + id + '" not found in document');
    }

    return template;
}

function getTemplate(template) {
    if (!template) { return; }

    return typeof template === 'string' ?
        // If template is an #id search for <template id="id">
        template[0] === '#' ? getTemplateById(template.slice(1)) :
        // It must be a string of HTML
        template :
    template.content ?
        // It must be a template node
        template :
        // Whatever it is, we don't support it
        function(){
            throw new Error('element() options.template not a template node, id or string');
        }() ;
}

function transferProperty(elem, key) {
    if (elem.hasOwnProperty(key)) {
        const value = elem[key];
        delete elem[key];
        elem[key] = value;
    }

    return elem;
}

function createShadow(template, elem, options) {
    if (!template) { return; }

    // Create a shadow root if there is DOM content. Shadows may be 'open' or
    // 'closed'. Closed shadows are not exposed via element.shadowRoot, and
    // events propagating from inside of them report the element as target.
    const shadow = elem.attachShadow({
        mode: options.mode || 'closed',
        delegatesFocus: true
    });

    elem[$shadow] = shadow;

    // If template is a <template>
    if (typeof template === 'string') {
        shadow.innerHTML = template;
    }
    else {
        shadow.appendChild(template.content.cloneNode(true));
    }

    return shadow;
}

function attachInternals(elem) {
    // Use native attachInternals where it exists
    if (elem.attachInternals) {
        return elem.attachInternals();
    }

    // Otherwise polyfill it with a pseudo internals object, actually a hidden
    // input that we put inside element (but outside the shadow DOM). We may
    // not yet put this in the DOM however – it violates the spec to give a
    // custom element children before it's contents are parsed. Instead we
    // wait until connectCallback.
    const hidden = create('input', { type: 'hidden', name: elem.name });

    // Polyfill internals object setFormValue
    hidden.setFormValue = function(value) {
        this.value = value;
    };

    return hidden;
}

function appendInternalsCallback() {
    // If we have simulated form internals, append the hidden input now
    if (this[$internals] && !this.attachInternals) {
        this.appendChild(this[$internals]);
    }
}

function primeAttributes(elem) {
    elem._initialAttributes = {};
    elem._n = 0;
}

function advanceAttributes(elem, attributes, handlers) {
    const values = elem._initialAttributes;

    while(elem._n < attributes.length && values[attributes[elem._n]] !== undefined) {
        //console.log('ADVANCE ATTR', attributes[elem._n]);
        handlers[attributes[elem._n]].call(elem, values[attributes[elem._n]]);
        ++elem._n;
    }
}

function flushAttributes(elem, attributes, handlers) {
    if (!elem._initialAttributes) { return; }

    const values = elem._initialAttributes;

    while(elem._n < attributes.length) {
        //console.log('FLUSH ATTR', attributes[elem._n]);
        if (values[attributes[elem._n]] !== undefined) {
            handlers[attributes[elem._n]].call(elem, values[attributes[elem._n]]);
        }
        ++elem._n;
    }

    delete elem._initialAttributes;
    delete elem._n;
}

export default function element(name, options) {
    // Get the element constructor from options.extends, or the
    // base HTMLElement constructor
    const constructor = options.extends ?
        getElementConstructor(options.extends) :
        HTMLElement ;

    let template;

    function Element() {
        // Get a template node or HTML string from options.template
        template = template || getTemplate(options.template);

        // Construct an instance from Constructor using the Element prototype
        const elem   = Reflect.construct(constructor, arguments, Element);
        const shadow = createShadow(template, elem, options);

        if (Element.formAssociated) {
            // Get access to the internal form control API
            elem[$internals] = attachInternals(elem);
        }

        options.construct && options.construct.call(elem, shadow);

        // Preserve initialisation order of attribute initialisation by
        // queueing them
        if (options.attributes) {
            primeAttributes(elem);

            // Wait a tick to flush attributes
            Promise.resolve(1).then(function() {
                flushAttributes(elem, Element.observedAttributes, options);
            });
        }

        // At this point, if properties have already been set before the
        // element was upgraded, they exist on the elem itself, where we have
        // just upgraded it's protytype to define those properties those
        // definitions will never be reached. Either:
        //
        // 1. Define properties on the instance instead of the prototype
        //    Object.defineProperties(elem, properties);
        //
        // 2. Take a great deal of care not to set properties before an element
        //    is upgraded. I can't impose a restriction like that.
        //
        // 3. Copy defined properties to their prototype handlers and delete
        //    them on the instance.
        //
        // Let's go with 3. I'm not happy you have to do this, though.
        options.properties
        && Object.keys(options.properties).reduce(transferProperty, elem);

        return elem;
    }

    // Properties
    //
    // Must be defined before attributeChangedCallback, but I cannot figure out
    // why. Where one of the properties is `value`, the element is set up as a
    // form element.

    if (options.properties && options.properties.value) {
        // Flag the Element class as formAssociated
        Element.formAssociated = true;

        Element.prototype = Object.create(constructor.prototype, assign({}, formProperties, options.properties, {
            value: {
                get: options.properties.value.get,
                set: function() {
                    // After setting value
                    options.properties.value.set.apply(this, arguments);

                    // Copy it to internal form state
                    this[$internals].setFormValue('' + this.value);
                }
            }
        })) ;
    }
    else {
        Element.prototype = Object.create(constructor.prototype, options.properties || {}) ;
    }

    // Attributes

    if (options.attributes) {
        Element.observedAttributes = Object.keys(options.attributes);

        Element.prototype.attributeChangedCallback = function(name, old, value) {
            if (!this._initialAttributes) {
                return options.attributes[name].call(this, value);
            }

            // Keep a record of attribute values to be applied in
            // observedAttributes order
            this._initialAttributes[name] = value;
            advanceAttributes(this, Element.observedAttributes, options.attributes);
        };
    }

    // Lifecycle

    Element.prototype.connectedCallback = function() {
        if (this._initialAttributes) {
            flushAttributes(this, Element.observedAttributes, options.attributes);
        }

        if (Element.formAssociated) {
            appendInternalsCallback.call(this);
        }

        if (options.connect) {
            options.connect.call(this, this[$shadow]);
        }

        if (DEBUG) { console.log('Connected to document:', this); }
    }

    if (options.disconnect) {
        Element.prototype.disconnectedCallback = function() {
            return options.disconnect.call(this, this[$shadow]);
        };
    }

    if (Element.formAssociated) {
        if (options.enable || options.disable) {
            Element.prototype.formDisabledCallback = function(disabled) {
                return disabled ?
                    options.disable && options.disable.call(this, this[$shadow]) :
                    options.enable && options.enable.call(this, this[$shadow]) ;
            };
        }

        if (options.reset) {
            Element.prototype.formResetCallback = function() {
                return options.reset.call(this, this[$shadow]);
            };
        }

        if (options.restore) {
            Element.prototype.formStateRestoreCallback = function() {
                return options.restore.call(this, this[$shadow]);
            };
        }
    }

    // Define element

    window.customElements.define(name, Element, options);
    return Element;
}
<|endoftext|>