<commit_before>var EventEmitter = require('events');

function debug (node, type, msg) {
    //if (type === 'WRITE' || type === 'LINK') {
        //console.log('DEBUG', '"' + type + '"', node._debugName, '|', msg);
    //}
}

module.exports = function Node (_event) {

    var node = new EventEmitter();
    node._corked = true;
    node._writableState = {needDrain: false};
    node.chunks = [];
    node.pipes = [];

    // ! debug
    node._debugName = _event[0] + '/' + _event[1];

    // write to input sequence
    node.write = function (chunk) {

debug(node, 'WRITE', chunk);

        if (!node._corked) {

debug(node, 'WRITE', 'pipe open');

            // emit data on node if no input is set
            if (node._i === null) {

debug(node, 'WRITE', 'no input');

                node.emit('data', chunk);
                return;
            }

            // write to input
            if (node._i) {

debug(node, 'WRITE', 'input exists');

                // cork node if input is full
                if (!node._i.write(chunk)) {

debug(node, 'WRITE', 'input full');
                    node._corked = true;
                    node._writableState.needDrain = true;
                }

                return;
            }
        }

debug(node, 'WRITE', 'buffer chunk');

        // buffer chunks until node is linked
        if (chunk !== undefined) {
            node.chunks.push(chunk);
        }
    };

    // ending
    node.end = function (chunk) {
        node.ended = true;

        if (node._i === null) {

debug(node, 'END', 'no input');

            return node.emit('end', chunk);
        }

        if (node._i) {

debug(node, 'END', 'end input');

            return node._i.end(chunk);
        }

debug(node, 'END', 'buffer chunks', typeof node._o);

        if (chunk !== undefined) {
            node.chunks.push(chunk);
        }
    };

    // pipe
    node.pipe = function (dest) {

        if (node._o) {
            return node._o.pipe(dest);
        }

        if (node._o === null) {
            node.on('data', dest.write.bind(dest));
            node.on('end', dest.end.bind(dest));
        }

        node.pipes.push(dest);
        return dest;
    };

    // unpipe
    node.unpipe = function (dest) {

        if (node._o) {
            return node._o.unpipe(dest);
        }

        // ..remove from node.pipes array
    };

    // build sequence
    node._seq = function (output) {

debug(node, 'SEQ', 'Output:' + typeof output);

        if (output) {
            //output.on('error', node.emit.bind(node, 'error'));
            node._i = node._i || output;
            node._o = node._o && output.write ? node._o.pipe(output) : output;

debug(node, 'SEQ', 'Output.write:' + typeof output.write);

        }
        return output;
    };

    // link sequence I/O to node
    node._link = function (instance, event) {

debug(node, 'LINK', 'Input: ' + typeof node._i + ' Output: ' + typeof node._o);

        var input = node._i = node._i || null;
        var output = node._o = node._o || null;
        node._r = event.r;
        node._e = event.e;
        node._corked = false;

// bufferd chunks?
// input? write to input

        // link input
        if (input) {

debug(node, 'LINK', 'input exists');

            node.chunks.forEach(input.write.bind(input));

            // write bufferd chunks to input after drain
            input.on('drain', function () {

debug(node, 'LINK', 'input drain');

                node._corked = false;
                node._writableState.needDrain = false;
                node.emit('drain');

                // emit buffered chunks
                node.chunks.forEach(input.write.bind(input));
                node.chunks = [];
            });
        }

        // bind output events to node
        if (output) {

            // end event
            output.on('end', function () {

debug(node, 'LINK', 'output ended');

                node.emit('end');
                node.removeAllListeners();
            });

            // error event
            output.on('error', function (err) {
                node.emit('error', err);
            });

            // data event
            output.on('data', node.emit.bind(node, 'data'));

            // pipe ouput
            if (node.pipes[0]) {
                node.pipes.forEach(output.pipe.bind(output));
            }

        } else if (node.pipes.length) {
            node.pipes.forEach(node.pipe.bind(node));
            node.pipes = [];
        }

        // write chunks to input if node has ended already
        if (node.ended) {
            if (input) {
                input.end(node.chunks.length ? undefined : 1);
            } else {
                node.chunks.forEach(node.emit.bind(node, 'data'));
                node.emit('end');
            }

            node.chunks = [];
        }

    };

    return node;
};
<commit_msg>handle writable outputs<commit_after>var EventEmitter = require('events');
var Stream = require('stream');

function debug (node, type, msg) {
    //if (type === 'WRITE' || type === 'LINK') {
        console.log('DEBUG', '"' + type + '"', node._debugName, '|', msg);
    //}
}

module.exports = function Node (_event) {

    var node = new EventEmitter();
    node._corked = true;
    node._writableState = {needDrain: false};
    node.chunks = [];
    node.pipes = [];

    // ! debug
    node._debugName = _event[0] + '/' + _event[1];

    // write to input sequence
    node.write = function (chunk) {

debug(node, 'WRITE', chunk);

        if (!node._corked) {

debug(node, 'WRITE', 'pipe open');

            // emit data on node if no input is set
            if (node._i === null) {

debug(node, 'WRITE', 'no input');

                node.emit('data', chunk);
                return;
            }

            // write to input
            if (node._i) {

debug(node, 'WRITE', 'input exists');

                // cork node if input is full
                if (!node._i.write(chunk)) {

debug(node, 'WRITE', 'input full');
                    node._corked = true;
                    node._writableState.needDrain = true;
                }

                return;
            }
        }

debug(node, 'WRITE', 'buffer chunk');

        // buffer chunks until node is linked
        if (chunk !== undefined) {
            node.chunks.push(chunk);
        }
    };

    // ending
    node.end = function (chunk) {
        node.ended = true;

        if (node._i === null) {

debug(node, 'END', 'no input');

            return node.emit('end', chunk);
        }

        if (node._i) {

debug(node, 'END', 'end input');

            return node._i.end(chunk);
        }

debug(node, 'END', 'buffer chunks', typeof node._o);

        if (chunk !== undefined) {
            node.chunks.push(chunk);
        }
    };

    // pipe
    node.pipe = function (dest) {

        if (node._o) {
            return node._o.pipe(dest);
        }

        if (node._o === null) {
            node.on('data', dest.write.bind(dest));
            node.on('end', dest.end.bind(dest));
        }

        node.pipes.push(dest);
        return dest;
    };

    // unpipe
    node.unpipe = function (dest) {

        if (node._o) {
            return node._o.unpipe(dest);
        }

        // ..remove from node.pipes array
    };

    // build sequence
    node._seq = function (output) {

debug(node, 'SEQ', 'Output:' + typeof output);

        if (output) {
            //output.on('error', node.emit.bind(node, 'error'));
            node._i = node._i || output;
            node._o = node._o && output.write ? node._o.pipe(output) : output;

debug(node, 'SEQ', 'Output.write:' + typeof output.write);

        }
        return output;
    };

    // link sequence I/O to node
    node._link = function (instance, event) {

debug(node, 'LINK', 'Input: ' + typeof node._i + ' Output: ' + typeof node._o);

        var input = node._i = node._i || null;
        var output = node._o = node._o || null;
        node._r = event.r;
        node._e = event.e;
        node._corked = false;

// bufferd chunks?
// input? write to input

        // link input
        if (input && input.write) {

debug(node, 'LINK', 'input exists');

            node.chunks.forEach(input.write.bind(input));

            // write bufferd chunks to input after drain
            input.on('drain', function () {

debug(node, 'LINK', 'input drain');

                node._corked = false;
                node._writableState.needDrain = false;
                node.emit('drain');

                // emit buffered chunks
                node.chunks.forEach(input.write.bind(input));
                node.chunks = [];
            });
        }

        // bind output events to node
        if (output) {

            // error event
            output.on('error', function (err) {
                node.emit('error', err);
            });

debug(node, 'LINK', 'output: pipe:' + typeof output.pipe + ', write: ' + typeof output.write);

            // handle writable output
            if (output instanceof Stream.Writable) {
                output.on('finish', function () {
debug(node, 'LINK', 'output finish (writable)')
                    node.emit('end');
                });
            } else {

                // end event
                output.on('end', function () {

debug(node, 'LINK', 'output ended');

                    node.emit('end');
                    node.removeAllListeners();
                });

                // data event
                output.on('data', node.emit.bind(node, 'data'));

                // pipe ouput
                if (node.pipes[0]) {
                    node.pipes.forEach(output.pipe.bind(output));
                }
            }

        } else if (node.pipes.length) {
            node.pipes.forEach(node.pipe.bind(node));
            node.pipes = [];
        }

        // write chunks to input if node has ended already
        if (node.ended) {
            if (input) {
                if (input.end) {
                    input.end(node.chunks.length ? undefined : 1);
                }
            } else {
                node.chunks.forEach(node.emit.bind(node, 'data'));
                node.emit('end');
            }

            node.chunks = [];
        }

    };

    return node;
};
<|endoftext|>


<commit_before>/*******************************
	OOPS Version 1.0.0

    A very simple "Object Oriented Programming Structured" class system for JavaScript

    Copyright (c) 2013 Greg McLeod  (Email: cleod9{at}gmail.com)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

	It is requested that you maintain all or part of this copyright notice even in the minified version.
*******************************/
var OOPS =
{
	_ID_: 0, //Unique ID for doing Class type checks (comparable via typeCheck function)
    _props_: {}, //A registry of all property names available in this Class (Required for extension)
	_parent_: null, //A reference to the parent Class
	disallowedTerms: ['_props_', '_parent_', '_statics_', '_definition_'], //Prevent override of these names (would break the functionality)
    extend: function (params) //Performs the actual Class creation
    {
        var p = null; //Temporary foreach loop var

        //Create a new empty function to populate (This is the Class that will be returned, direct descendant of OOPS)
        var cls = function()
        {
            //If arguments are passed to the function and a _constructor_ method is provided, those arguments will be passed to _constructor_
			if(typeof this._constructor_ == 'function')
                this._constructor_.apply(this, arguments); //Apply arguments to the provided constructor
            if(arguments)
				this._constructor_ = function() {}; //No constructor provided, create a dummy constructor
        };

		//Grant unique class ID
		cls.prototype._ID_ = (++OOPS._ID_);

		//Provide other prototypes to the class
        cls.prototype._props_ = {}; //Gives this new Class access to properties without using individual standard JavaScript prototype overrides
        cls.prototype._parent_ = this; //Gives new class direct access to the parent

        //Inherit parent info
        for(p in this._props_)
            if(!params[p])
                params[p] = this._props_[p]; //Only overwrite if it doesn't exist in the new parameters list

        //For each item in the updated params list
        for(p in params)
        {
            //If the term is not banned
            if(OOPS.disallowedTerms.indexOf(p) < 0)
            {
                //Add the parameter to this new Class instance's prototype
				cls.prototype[p] = params[p];
				//Saving all the props in case we need for later, such as binding the functions to a  class itself when we instantiate
				cls.prototype._props_[p] = params[p];
            }
        }

		//Prep variables for future class extension
		cls._ID_ = (OOPS._ID_); //Use the ID that was created in the earlier prototype
        cls.extend = this.extend; //Inherit the extend method from OOPS
        cls._props_ = params; //Remember all of the properties inherited so far
		cls._parent_ = this; //Set the class parent to this

		//Add static content to class, binding functions as needed
        if(typeof params._statics_ == 'object')
		{
			//If a param called _statics_ was provided, it will treat this as a list of static properties for the object
			for(p in params._statics_)
			{
				//As long as the property name is not disallowed
				if(OOPS.disallowedTerms.indexOf(p) < 0)
				{
					//Set the static property onto the Class itself
					cls[p] = params._statics_[p];
					//If the property was a function, bind the function to the Class itself
					if(typeof cls[p] == 'function')
						cls[p] = cls[p].bind(cls);
				}
			}
		}
		//Return the class
        return cls;
    }, typeMatch: function (class1, class2)
	{
		//If the classes have matching _ID_ fields they are of the same type
		return (class1._ID_ == class2._ID_);
	}, descendantOf: function (class1, class2)
	{
		//Recursively traverse back up until we find a valid ancestor
		if(class1._parent_)
		{
			if(class1._parent_._ID_ == class2._ID_)
				return true;
			else
				return OOPS.descendantOf(class1._parent_, class2);
		}
		else
			return false;
	}
};<commit_msg>Version 1.1.0 Update -Greatly simplified the setup structure by using standard JS practices -Replaced _parent_ property with _super_ -No longer a need to specific the name of the parent class to call its constructor<commit_after>/*******************************
	OOPS Version 1.1.0

    A very simple "Object Oriented Programming Structured" class system for JavaScript

  	The MIT License (MIT)

	Copyright (c) 2013 Greg McLeod <cleod9{at}gmail.com>

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
*******************************/
var OOPS = (function() {
	//Constructor
	function OOPS() {}; //Unique ID for doing Class type checks (comparable via OOPS.typeMatch())
	//Extender (copy all of the prototypes from parent to child)
	OOPS._ID_ = 0;
	OOPS.extend = function(props) {
		var i;
		//Disallow these prop names
		var reserved = ['_statics_', '_constructor_', '_super_'];
		//Create a new empty function to populate (This is the Class that will be returned)
		//Note: "_constructor_" will be used as the base if provided)
		var child = (typeof props._constructor_ == 'function') ? props._constructor_ : function() {};
		//Child will use provided fields as their prototype
		child.prototype = (typeof props == 'object') ? props : {};
		//Child inherits the fields from parent that it doesn't have yet
		for(i in this.prototype)
			if(!child.prototype.hasOwnProperty(i))
				child.prototype[i] = this.prototype[i];
		//Statics get placed onto the child itself
		if(child.prototype._statics_) {
			for(i in child.prototype._statics_)
				child[i] = child.prototype._statics_[i];
			//Remove _statics_ from the prototype
			delete child.prototype._statics_;
		}
		//Pass down the extend method
		child.extend = this.extend;
		//Pass down the _super_ method (if applicable)
		child.prototype._super_ = child._super_ = (this != OOPS) ? this : null;
		//Grant unique class ID
		child.prototype._ID_ = child._ID_ = ++OOPS._ID_;
		//Return the completed class
		return child;
	};
	OOPS.typeMatch = function(class1, class2) {
		//If the classes have matching _ID_ fields they are of the same type
		//Note: Classes have _ID_ as a static and prototype value, use instanceof for strict type testing
		return (class1._ID_ == class2._ID_);
	};
	OOPS.descendantOf = function (child, parent) {
		//Recursively traverse back up until we find a valid ancestor
		//Note: Classes have _ID_ as a static and prototype value, use instanceof for strict type testing
		if(child._super_) {
			if(child._super_._ID_ == parent._ID_)
				return true;
			else
				return OOPS.descendantOf(child._super_, parent);
		}
		return false;
	};
	//Return
	return OOPS;
})();<|endoftext|>


<commit_before>/*jshint esnext:true*/
var fs = require("fs"),
    cards = require("./Cards"),
    Grid = require("./Grid"),
    Decks = require("./Decks.js"),
    Hand = require("./Hand.js");

/**
 * Represents a game
 * @param {Room} room     The parent Room
 * @param {[String]} cardSets List of card sets to use
 */
function Game(room,cardSets){
  this.room = room;
  this.hands = [];
  this.grid = new Grid(this);
  this.decks = new Decks(this);
  this.cardList = [];
  cardSets.forEach(this.loadCards.bind(this));
  this.setupDecks();

  //---- DEMO ----
  this.grid.addCard([0,0],this.cardList[0]);
  for(var i=0;i<this.decks.ponyCards.length;i++){
    if (this.decks.ponyCards[i].id === 0) break;
  }
  this.decks.ponyCards.splice(i,1);
  this.activePlayer = 0;
  //---- END DEMO ----
}

Game.prototype.newClient = function(client){
  var newHand = new Hand(client,this);
  newHand.drawCards(4,3);
  client.curHand = newHand;
  this.hands.push(newHand);
};

Game.prototype.isActivePlayer = function(client){
  return this.hands[this.activePlayer].client == client;
};

/**
 * Loads a card set from the given file and adds them to the usable card List
 * @param  {String}   file    File location to load the card list from
 */
Game.prototype.loadCards = function(file){
  var that = this;
  JSON.parse(fs.readFileSync(file)).forEach(function(rawCard){
    var card = cards.Card.fromObject(rawCard);
    card.id = that.cardList.length;
    if("special" in rawCard){ //XXX Special hack
      card.special = rawCard.special;
      console.log(JSON.stringify(card));
    }
    that.cardList.push(card);
  });
};

Game.prototype.setupDecks = function(){
  for(var i=0;i<this.cardList.length;i++){
    this.decks.addCard(i);
  }
  this.decks.shuffleShips();
  this.decks.shuffleGoals();
  this.decks.shufflePonies();
};

/**
 * Event handler to be called when a player plays a cards
 * Calculates all the results of the play then returns what needs to be done
 * @param  {Array}    Cards   List of Card id's and positions that where played
 * @param  {string}   Effect  The name of the effect that has been triggered by the play
 * @param  {[object]} Params  List of paramaters for the triggered effect, varies basied on effect
 * @param  {Client}   Client  The client who made the play
 * @return {Array}            List of Card id's and positions that should be reported back to the client to update their grid
 */
Game.prototype.onPlay = function(cards,params,client){

  //XXX Should be tidied up, im thinking an assert statment, that might be helpful
  var cc = this.hands[this.activePlayer].client;
  if(cc != client){
    client.send({type:"error",msg:"Not your turn, it is "+cc.name+" turn"});
    return;
  }

  if(params.length > 0 && this.cardList[params[0]].effect == "replace"){
    this.grid.replaceCard(params[1],params.shift());
    return [{id:params.shift(),position:null},cards[0]];
  } else {
    cards.forEach(c => this.grid.addCard(c.position,c.id));
    return cards.concat(this.resolveEffect(params,client));
  }
};

Game.prototype.resolveEffect = function(params,client){
  if(params.length === 0){
    console.warn("Empty effect paramaters - this is only normal if no effect triggered");
    return [];
  }
  var effect = this.cardList[params.shift()].effect;
  if(effect in this.effects){
    return this.effects[effect].call(this,params,client);
  } else {
    console.warn("No handler for effect",effect);
    return [];
  }
};

Game.prototype.effects = {
  swap:function(params,client){
    var swappedCards = this.grid.swapCards([params[0],params[1]]);
    return [].concat(
      swappedCards.map(c=>({id:c.id,position:null})), //Mark cards to remove
      swappedCards.map(c=>({id:c.id,position:c.position})) //Readd cards
    );
  },
  draw:function(params,client){
    var type = params.shift(),drawnCards,
        nPonies = type=="pony"? 1:0;
    client.curHand.drawCards(nPonies,1-nPonies);
    return [];
  },
  copy:Game.prototype.resolveEffect
};

/*Game hooks, checked after room */
Game.prototype.hooks = {};

Game.prototype.hooks.playCards = function(data,client){
  data.cards = this.onPlay(data.cards,data.params,client);
  data.client = client;
  data.type = "playCards";
  this.room.broadcast(data);
}

Game.prototype.hooks.reSyncGrid = function(data,client){
  client.send(this.room.packet("gridState"));
}

Game.prototype.hooks.endTurn = function(data,client){
  if (!this.isActivePlayer(client)){
    client.send({type:"error",msg:"Not your turn, it is "+cc.name+" turn"});
    return
  }
  var cHand = client.curHand,
    handSizeNow = cHand.ponies.length + cHand.ships.length,
    handSizeAfter = handSizeNow + data.ponies + data.ships
  if( handSizeAfter != 7){ //MAGIC Number
    client.send({type:"error",msg:"Attempt to draw wrong number of cards"});
    return;
  }
  client.curHand.drawCards(data.ponies, data.ships)
  this.activePlayer = (this.activePlayer+1)%this.hands.length;
  //TODO Add turnStart message
}

/*Game Packets - Called from Room.packet()*/
Game.prototype.packets = {};
Game.prototype.packets.cardList = function(){
  return {cardList:this.cardList};
};
Game.prototype.packets.gridState = function(){
  var grid=this.grid;
  return {grid:[].concat(
    Object.keys(grid.ponies).map(function(n){var c=grid.ponies[n];return {id:c.id,position:c.position};}),
    Object.keys(grid.ships).map(function(n){var c=grid.ships[n];return {id:c.id,position:c.position};})
  )};
};

module.exports = Game;
<commit_msg>more linting fixes<commit_after>/*jshint esnext:true*/
var fs = require("fs"),
    cards = require("./Cards"),
    Grid = require("./Grid"),
    Decks = require("./Decks.js"),
    Hand = require("./Hand.js");

/**
 * Represents a game
 * @param {Room} room     The parent Room
 * @param {[String]} cardSets List of card sets to use
 */
function Game(room,cardSets){
  this.room = room;
  this.hands = [];
  this.grid = new Grid(this);
  this.decks = new Decks(this);
  this.cardList = [];
  cardSets.forEach(this.loadCards.bind(this));
  this.setupDecks();

  //---- DEMO ----
  this.grid.addCard([0,0],this.cardList[0]);
  for(var i=0;i<this.decks.ponyCards.length;i++){
    if (this.decks.ponyCards[i].id === 0) break;
  }
  this.decks.ponyCards.splice(i,1);
  this.activePlayer = 0;
  //---- END DEMO ----
}

Game.prototype.newClient = function(client){
  var newHand = new Hand(client,this);
  newHand.drawCards(4,3);
  client.curHand = newHand;
  this.hands.push(newHand);
};

Game.prototype.isActivePlayer = function(client){
  return this.hands[this.activePlayer].client == client;
};

/**
 * Loads a card set from the given file and adds them to the usable card List
 * @param  {String}   file    File location to load the card list from
 */
Game.prototype.loadCards = function(file){
  var that = this;
  JSON.parse(fs.readFileSync(file)).forEach(function(rawCard){
    var card = cards.Card.fromObject(rawCard);
    card.id = that.cardList.length;
    if("special" in rawCard){ //XXX Special hack
      card.special = rawCard.special;
      console.log(JSON.stringify(card));
    }
    that.cardList.push(card);
  });
};

Game.prototype.setupDecks = function(){
  for(var i=0;i<this.cardList.length;i++){
    this.decks.addCard(i);
  }
  this.decks.shuffleShips();
  this.decks.shuffleGoals();
  this.decks.shufflePonies();
};

/**
 * Event handler to be called when a player plays a cards
 * Calculates all the results of the play then returns what needs to be done
 * @param  {Array}    Cards   List of Card id's and positions that where played
 * @param  {string}   Effect  The name of the effect that has been triggered by the play
 * @param  {[object]} Params  List of paramaters for the triggered effect, varies basied on effect
 * @param  {Client}   Client  The client who made the play
 * @return {Array}            List of Card id's and positions that should be reported back to the client to update their grid
 */
Game.prototype.onPlay = function(cards,params,client){

  //XXX Should be tidied up, im thinking an assert statment, that might be helpful
  var cc = this.hands[this.activePlayer].client;
  if(cc != client){
    client.send({type:"error",msg:"Not your turn, it is "+cc.name+" turn"});
    return;
  }

  if(params.length > 0 && this.cardList[params[0]].effect == "replace"){
    this.grid.replaceCard(params[1],params.shift());
    return [{id:params.shift(),position:null},cards[0]];
  } else {
    cards.forEach(c => this.grid.addCard(c.position,c.id));
    return cards.concat(this.resolveEffect(params,client));
  }
};

Game.prototype.resolveEffect = function(params,client){
  if(params.length === 0){
    console.warn("Empty effect paramaters - this is only normal if no effect triggered");
    return [];
  }
  var effect = this.cardList[params.shift()].effect;
  if(effect in this.effects){
    return this.effects[effect].call(this,params,client);
  } else {
    console.warn("No handler for effect",effect);
    return [];
  }
};

Game.prototype.effects = {
  swap:function(params,client){
    var swappedCards = this.grid.swapCards([params[0],params[1]]);
    return [].concat(
      swappedCards.map(c=>({id:c.id,position:null})), //Mark cards to remove
      swappedCards.map(c=>({id:c.id,position:c.position})) //Readd cards
    );
  },
  draw:function(params,client){
    var type = params.shift(),drawnCards,
        nPonies = type=="pony"? 1:0;
    client.curHand.drawCards(nPonies,1-nPonies);
    return [];
  },
  copy:Game.prototype.resolveEffect
};

/*Game hooks, checked after room */
Game.prototype.hooks = {};

Game.prototype.hooks.playCards = function(data,client){
  data.cards = this.onPlay(data.cards,data.params,client);
  data.client = client;
  data.type = "playCards";
  this.room.broadcast(data);
};

Game.prototype.hooks.reSyncGrid = function(data,client){
  client.send(this.room.packet("gridState"));
};

Game.prototype.hooks.endTurn = function(data,client){
  if (!this.isActivePlayer(client)){
    client.send({type:"error",msg:"Not your turn, it is "+cc.name+" turn"});
    return;
  }
  var cHand = client.curHand,
    handSizeNow = cHand.ponies.length + cHand.ships.length,
    handSizeAfter = handSizeNow + data.ponies + data.ships;
  if( handSizeAfter != 7){ //MAGIC Number
    client.send({type:"error",msg:"Attempt to draw wrong number of cards"});
    return;
  }
  client.curHand.drawCards(data.ponies, data.ships);
  this.activePlayer = (this.activePlayer+1)%this.hands.length;
  //TODO Add turnStart message
};

/*Game Packets - Called from Room.packet()*/
Game.prototype.packets = {};
Game.prototype.packets.cardList = function(){
  return {cardList:this.cardList};
};
Game.prototype.packets.gridState = function(){
  var grid=this.grid;
  return {grid:[].concat(
    Object.keys(grid.ponies).map(function(n){var c=grid.ponies[n];return {id:c.id,position:c.position};}),
    Object.keys(grid.ships).map(function(n){var c=grid.ships[n];return {id:c.id,position:c.position};})
  )};
};

module.exports = Game;
<|endoftext|>


<commit_before>/* =============================================================
 * bootstrap-typeahead.js v2.1.0
 * http://twitter.github.com/bootstrap/javascript.html#typeahead
 * =============================================================
 * Copyright 2012 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ============================================================ */

/* =============================================================
 * Zulip's custom changes
 *
 * 1. Automated selection:
 *
 *   This adds support for automatically selecting a typeahead (on certain
 *   completions or queries). If `this.automated` returns true, we do not
 *   render the typeahead and directly trigger selection of the current
 *   choice.
 *
 *   Our custom changes include all mentions of this.automated.
 *   And also includes the blocks containing the is contenteditable condition.
 *
 * 2. Custom selection triggers:
 *
 *   This adds support for completing a typeahead on custom keyup input. By
 *   default, we only support Tab and Enter to complete a typeahead, but we
 *   have usecases where we want to complete using custom characters like: >.
 *
 *   If `this.trigger_selection` returns true, we complete the typeahead and
 *   pass the keyup event to the updater.
 *
 *   Our custom changes include all mentions of this.trigger_selection.
 *
 * 3. Header text:
 *
 *   This adds support for showing a custom header text like: "You are now
 *   completing a user mention". Provide the function `this.header` that
 *   returns a string containing the header text, or false.
 *
 *   Our custom changes include all mentions of this.header, some CSS changes
 *   in compose.scss and splitting $container out of $menu so we can insert
 *   additional HTML before $menu.
 *
 * 4. Escape hooks:
 *
 *  You can set an on_escape hook to take extra actions when the user hits
 *  the `Esc` key.  We use this in our navbar code to close the navbar when
 *  a user hits escape while in the typeahead.
 *
 * 5. Help on empty strings:
 *
 *   This adds support for displaying the typeahead for an empty string.
 *   It is helpful when we want to render the typeahead, based on already
 *   entered data (in the form of contenteditable elements) every time the
 *   input block gains focus but is empty.
 *
 *   We also have logic so that there is an exception to this rule when this
 *   option is set as true. We prevent the lookup of the typeahead and hide it
 *   so that the `Backspace` key is free to interact with the other elements.
 *
 *   Our custom changes include all mentions of `helpOnEmptyStrings` and `hideOnEmpty`.
 * ============================================================ */

!function($){

  "use strict"; // jshint ;_;

 /* TYPEAHEAD PUBLIC CLASS DEFINITION
  * ================================= */

  var Typeahead = function (element, options) {
    this.$element = $(element)
    this.options = $.extend({}, $.fn.typeahead.defaults, options)
    this.matcher = this.options.matcher || this.matcher
    this.sorter = this.options.sorter || this.sorter
    this.highlighter = this.options.highlighter || this.highlighter
    this.updater = this.options.updater || this.updater
    this.$container = $(this.options.container).appendTo('body')
    this.$menu = $(this.options.menu).appendTo(this.$container)
    this.$header = $(this.options.header_html).appendTo(this.$container)
    this.source = this.options.source
    this.shown = false
    this.dropup = this.options.dropup
    this.fixed = this.options.fixed || false;
    this.automated = this.options.automated || this.automated;
    this.trigger_selection = this.options.trigger_selection || this.trigger_selection;
    this.on_move = this.options.on_move;
    this.on_escape = this.options.on_escape;
    this.header = this.options.header || this.header;

    if (this.fixed) {
      this.$container.css('position', 'fixed');
    }
    // The naturalSearch option causes arrow keys to immediately
    // update the search box with the underlying values from the
    // search suggestions.
    this.listen()
  }

  Typeahead.prototype = {

    constructor: Typeahead

  , select: function (e) {
      var val = this.$menu.find('.active').data('typeahead-value')
      if (this.$element.is("[contenteditable]")) {
        this.$element.html(this.updater(val, e)).trigger("change");
        // Empty textContent after the change event handler
        // converts the input text to html elements.
        this.$element.html('');
      } else {
        this.$element.val(this.updater(val, e)).trigger("change");
      }

      return this.hide()
    }

  , set_value: function () {
      var val = this.$menu.find('.active').data('typeahead-value')
      this.$element.is("[contenteditable]") ? this.$element.html(val) : this.$element.val(val);

      if (this.on_move) {
        this.on_move();
      }
    }

  , updater: function (item) {
      return item
    }

  , automated: function() {
    return false;
  }

  , trigger_selection: function() {
    return false;
  }

  , header: function() {
    // return a string to show in typeahead header or false.
    return false;
  }

  , show: function () {
      var pos;

      if (this.fixed) {
        // Relative to screen instead of to page
        pos = this.$element[0].getBoundingClientRect();
      } else {
        pos = this.$element.offset();
      }

      pos = $.extend({}, pos, {
        height: this.$element[0].offsetHeight
      })

      // Zulip patch: Workaround for iOS safari problems
      pos.top = this.$element.offset().top;

      var top_pos = pos.top + pos.height
      if (this.dropup) {
        top_pos = pos.top - this.$container.outerHeight()
      }

      this.$container.css({
        top: top_pos
       , left: pos.left
      })

      var header_text = this.header();
      if (header_text) {
        this.$header.find('span#typeahead-header-text').html(header_text);
        this.$header.show();
      } else {
        this.$header.hide();
      }

      this.$container.show()
      this.shown = true
      return this
    }

  , hide: function () {
      this.$container.hide()
      this.shown = false
      return this
    }

  , lookup: function (hideOnEmpty) {
      var items

      this.query = this.$element.is("[contenteditable]") ? this.$element.text() :  this.$element.val();

      if (!this.options.helpOnEmptyStrings || hideOnEmpty) {
        if (!this.query || this.query.length < this.options.minLength) {
          return this.shown ? this.hide() : this
        }
      }

      items = typeof this.source === "function" ? this.source(this.query, this.process.bind(this)) : this.source

      if (!items && this.shown) this.hide();
      return items ? this.process(items) : this
    }

  , process: function (items) {
      var that = this

      items = $.grep(items, function (item) {
        return that.matcher(item)
      })

      items = this.sorter(items)

      if (!items.length) {
        return this.shown ? this.hide() : this
      }
      if (this.automated()) {
        this.select();
        this.lookup();
        return this;
      }
      return this.render(items.slice(0, this.options.items)).show()
    }

  , matcher: function (item) {
      return ~item.toLowerCase().indexOf(this.query.toLowerCase())
    }

  , sorter: function (items) {
      var beginswith = []
        , caseSensitive = []
        , caseInsensitive = []
        , item

      while (item = items.shift()) {
        if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
        else if (~item.indexOf(this.query)) caseSensitive.push(item)
        else caseInsensitive.push(item)
      }

      return beginswith.concat(caseSensitive, caseInsensitive)
    }

  , highlighter: function (item) {
      var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
      return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
        return '' + match + ''
      })
    }

  , render: function (items) {
      var that = this

      items = $(items).map(function (i, item) {
        i = $(that.options.item).data('typeahead-value', item)
        i.find('a').html(that.highlighter(item))
        return i[0]
      })

      items.first().addClass('active')
      this.$menu.html(items)
      return this
    }

  , next: function (event) {
      var active = this.$menu.find('.active').removeClass('active')
        , next = active.next()

      if (!next.length) {
        next = $(this.$menu.find('li')[0])
      }

      next.addClass('active')

      if (this.options.naturalSearch) {
        this.set_value();
      }
    }

  , prev: function (event) {
      var active = this.$menu.find('.active').removeClass('active')
        , prev = active.prev()

      if (!prev.length) {
        prev = this.$menu.find('li').last()
      }

      prev.addClass('active')

      if (this.options.naturalSearch) {
        this.set_value();
      }
    }

  , listen: function () {
      this.$element
        .on('blur',     this.blur(this))
        .on('keypress', this.keypress.bind(this))
        .on('keyup',    this.keyup.bind(this))

      if (this.eventSupported('keydown')) {
        this.$element.on('keydown', this.keydown.bind(this))
      }

      this.$menu
        .on('click', this.click.bind(this))
        .on('mouseenter', 'li', this.mouseenter.bind(this))
    }

  , eventSupported: function(eventName) {
      var isSupported = eventName in this.$element
      if (!isSupported) {
        this.$element.setAttribute(eventName, 'return;')
        isSupported = typeof this.$element[eventName] === 'function'
      }
      return isSupported
    }

  , move: function (e) {
      if (!this.shown) return

      switch(e.keyCode) {
        case 9: // tab
        case 13: // enter
        case 27: // escape
          e.preventDefault()
          break

        case 38: // up arrow
          e.preventDefault()
          this.prev()
          break

        case 40: // down arrow
          e.preventDefault()
          this.next()
          break
      }

      if ((this.options.stopAdvance || (e.keyCode != 9 && e.keyCode != 13))
          && $.inArray(e.keyCode, this.options.advanceKeyCodes)) {
          e.stopPropagation()
      }
    }

  , keydown: function (e) {
      this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
      this.move(e)
    }

  , keypress: function (e) {
      if (this.suppressKeyPressRepeat) return
      this.move(e)
    }

  , keyup: function (e) {
      switch(e.keyCode) {
        case 40: // down arrow
        case 38: // up arrow
          break

        case 9: // tab
        case 13: // enter
          if (!this.shown) return
          this.select(e)
          break

        case 27: // escape
          if (!this.shown) return
          this.hide()
          if (this.on_escape) {
            this.on_escape();
          }
          break

        default:
          if (this.trigger_selection(e)) {
            if (!this.shown) return;
            this.select(e);
          }
          var hideOnEmpty = false
          if (e.keyCode === 8 && this.options.helpOnEmptyStrings) { // backspace
            hideOnEmpty = true
          }
          this.lookup(hideOnEmpty)
      }

      if ((this.options.stopAdvance || (e.keyCode != 9 && e.keyCode != 13))
          && $.inArray(e.keyCode, this.options.advanceKeyCodes)) {
          e.stopPropagation()
      }

      e.preventDefault()
  }

  , blur: function (e) {
      var that = this
      setTimeout(function () {
        if (!that.$container.is(':hover')) {
          that.hide();
        }
      }, 150)
    }

  , click: function (e) {
      e.stopPropagation()
      e.preventDefault()
      this.select(e)
    }

  , mouseenter: function (e) {
      this.$menu.find('.active').removeClass('active')
      $(e.currentTarget).addClass('active')
    }

  }

  /* TYPEAHEAD PLUGIN DEFINITION
   * =========================== */

  $.fn.typeahead = function (option) {
    return this.each(function () {
      var $this = $(this)
        , data = $this.data('typeahead')
        , options = typeof option == 'object' && option
      if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  $.fn.typeahead.defaults = {
    source: []
  , items: 8
  , container: '

'
  , header_html: '

'
  , menu: '

'
  , item: '

'
  , minLength: 1
  , stopAdvance: false
  , dropup: false
  , advanceKeyCodes: []
  }

  $.fn.typeahead.Constructor = Typeahead

 /*   TYPEAHEAD DATA-API
  * ================== */

  $(function () {
    $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
      var $this = $(this)
      if ($this.data('typeahead')) return
      e.preventDefault()
      $this.typeahead($this.data())
    })
  })

}(window.jQuery);
<commit_msg>typeahead: Fix binding of event handler to `blur` event.<commit_after>/* =============================================================
 * bootstrap-typeahead.js v2.1.0
 * http://twitter.github.com/bootstrap/javascript.html#typeahead
 * =============================================================
 * Copyright 2012 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ============================================================ */

/* =============================================================
 * Zulip's custom changes
 *
 * 1. Automated selection:
 *
 *   This adds support for automatically selecting a typeahead (on certain
 *   completions or queries). If `this.automated` returns true, we do not
 *   render the typeahead and directly trigger selection of the current
 *   choice.
 *
 *   Our custom changes include all mentions of this.automated.
 *   And also includes the blocks containing the is contenteditable condition.
 *
 * 2. Custom selection triggers:
 *
 *   This adds support for completing a typeahead on custom keyup input. By
 *   default, we only support Tab and Enter to complete a typeahead, but we
 *   have usecases where we want to complete using custom characters like: >.
 *
 *   If `this.trigger_selection` returns true, we complete the typeahead and
 *   pass the keyup event to the updater.
 *
 *   Our custom changes include all mentions of this.trigger_selection.
 *
 * 3. Header text:
 *
 *   This adds support for showing a custom header text like: "You are now
 *   completing a user mention". Provide the function `this.header` that
 *   returns a string containing the header text, or false.
 *
 *   Our custom changes include all mentions of this.header, some CSS changes
 *   in compose.scss and splitting $container out of $menu so we can insert
 *   additional HTML before $menu.
 *
 * 4. Escape hooks:
 *
 *  You can set an on_escape hook to take extra actions when the user hits
 *  the `Esc` key.  We use this in our navbar code to close the navbar when
 *  a user hits escape while in the typeahead.
 *
 * 5. Help on empty strings:
 *
 *   This adds support for displaying the typeahead for an empty string.
 *   It is helpful when we want to render the typeahead, based on already
 *   entered data (in the form of contenteditable elements) every time the
 *   input block gains focus but is empty.
 *
 *   We also have logic so that there is an exception to this rule when this
 *   option is set as true. We prevent the lookup of the typeahead and hide it
 *   so that the `Backspace` key is free to interact with the other elements.
 *
 *   Our custom changes include all mentions of `helpOnEmptyStrings` and `hideOnEmpty`.
 * ============================================================ */

!function($){

  "use strict"; // jshint ;_;

 /* TYPEAHEAD PUBLIC CLASS DEFINITION
  * ================================= */

  var Typeahead = function (element, options) {
    this.$element = $(element)
    this.options = $.extend({}, $.fn.typeahead.defaults, options)
    this.matcher = this.options.matcher || this.matcher
    this.sorter = this.options.sorter || this.sorter
    this.highlighter = this.options.highlighter || this.highlighter
    this.updater = this.options.updater || this.updater
    this.$container = $(this.options.container).appendTo('body')
    this.$menu = $(this.options.menu).appendTo(this.$container)
    this.$header = $(this.options.header_html).appendTo(this.$container)
    this.source = this.options.source
    this.shown = false
    this.dropup = this.options.dropup
    this.fixed = this.options.fixed || false;
    this.automated = this.options.automated || this.automated;
    this.trigger_selection = this.options.trigger_selection || this.trigger_selection;
    this.on_move = this.options.on_move;
    this.on_escape = this.options.on_escape;
    this.header = this.options.header || this.header;

    if (this.fixed) {
      this.$container.css('position', 'fixed');
    }
    // The naturalSearch option causes arrow keys to immediately
    // update the search box with the underlying values from the
    // search suggestions.
    this.listen()
  }

  Typeahead.prototype = {

    constructor: Typeahead

  , select: function (e) {
      var val = this.$menu.find('.active').data('typeahead-value')
      if (this.$element.is("[contenteditable]")) {
        this.$element.html(this.updater(val, e)).trigger("change");
        // Empty textContent after the change event handler
        // converts the input text to html elements.
        this.$element.html('');
      } else {
        this.$element.val(this.updater(val, e)).trigger("change");
      }

      return this.hide()
    }

  , set_value: function () {
      var val = this.$menu.find('.active').data('typeahead-value')
      this.$element.is("[contenteditable]") ? this.$element.html(val) : this.$element.val(val);

      if (this.on_move) {
        this.on_move();
      }
    }

  , updater: function (item) {
      return item
    }

  , automated: function() {
    return false;
  }

  , trigger_selection: function() {
    return false;
  }

  , header: function() {
    // return a string to show in typeahead header or false.
    return false;
  }

  , show: function () {
      var pos;

      if (this.fixed) {
        // Relative to screen instead of to page
        pos = this.$element[0].getBoundingClientRect();
      } else {
        pos = this.$element.offset();
      }

      pos = $.extend({}, pos, {
        height: this.$element[0].offsetHeight
      })

      // Zulip patch: Workaround for iOS safari problems
      pos.top = this.$element.offset().top;

      var top_pos = pos.top + pos.height
      if (this.dropup) {
        top_pos = pos.top - this.$container.outerHeight()
      }

      this.$container.css({
        top: top_pos
       , left: pos.left
      })

      var header_text = this.header();
      if (header_text) {
        this.$header.find('span#typeahead-header-text').html(header_text);
        this.$header.show();
      } else {
        this.$header.hide();
      }

      this.$container.show()
      this.shown = true
      return this
    }

  , hide: function () {
      this.$container.hide()
      this.shown = false
      return this
    }

  , lookup: function (hideOnEmpty) {
      var items

      this.query = this.$element.is("[contenteditable]") ? this.$element.text() :  this.$element.val();

      if (!this.options.helpOnEmptyStrings || hideOnEmpty) {
        if (!this.query || this.query.length < this.options.minLength) {
          return this.shown ? this.hide() : this
        }
      }

      items = typeof this.source === "function" ? this.source(this.query, this.process.bind(this)) : this.source

      if (!items && this.shown) this.hide();
      return items ? this.process(items) : this
    }

  , process: function (items) {
      var that = this

      items = $.grep(items, function (item) {
        return that.matcher(item)
      })

      items = this.sorter(items)

      if (!items.length) {
        return this.shown ? this.hide() : this
      }
      if (this.automated()) {
        this.select();
        this.lookup();
        return this;
      }
      return this.render(items.slice(0, this.options.items)).show()
    }

  , matcher: function (item) {
      return ~item.toLowerCase().indexOf(this.query.toLowerCase())
    }

  , sorter: function (items) {
      var beginswith = []
        , caseSensitive = []
        , caseInsensitive = []
        , item

      while (item = items.shift()) {
        if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
        else if (~item.indexOf(this.query)) caseSensitive.push(item)
        else caseInsensitive.push(item)
      }

      return beginswith.concat(caseSensitive, caseInsensitive)
    }

  , highlighter: function (item) {
      var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
      return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
        return '' + match + ''
      })
    }

  , render: function (items) {
      var that = this

      items = $(items).map(function (i, item) {
        i = $(that.options.item).data('typeahead-value', item)
        i.find('a').html(that.highlighter(item))
        return i[0]
      })

      items.first().addClass('active')
      this.$menu.html(items)
      return this
    }

  , next: function (event) {
      var active = this.$menu.find('.active').removeClass('active')
        , next = active.next()

      if (!next.length) {
        next = $(this.$menu.find('li')[0])
      }

      next.addClass('active')

      if (this.options.naturalSearch) {
        this.set_value();
      }
    }

  , prev: function (event) {
      var active = this.$menu.find('.active').removeClass('active')
        , prev = active.prev()

      if (!prev.length) {
        prev = this.$menu.find('li').last()
      }

      prev.addClass('active')

      if (this.options.naturalSearch) {
        this.set_value();
      }
    }

  , listen: function () {
      this.$element
        .on('blur',     this.blur.bind(this))
        .on('keypress', this.keypress.bind(this))
        .on('keyup',    this.keyup.bind(this))

      if (this.eventSupported('keydown')) {
        this.$element.on('keydown', this.keydown.bind(this))
      }

      this.$menu
        .on('click', this.click.bind(this))
        .on('mouseenter', 'li', this.mouseenter.bind(this))
    }

  , eventSupported: function(eventName) {
      var isSupported = eventName in this.$element
      if (!isSupported) {
        this.$element.setAttribute(eventName, 'return;')
        isSupported = typeof this.$element[eventName] === 'function'
      }
      return isSupported
    }

  , move: function (e) {
      if (!this.shown) return

      switch(e.keyCode) {
        case 9: // tab
        case 13: // enter
        case 27: // escape
          e.preventDefault()
          break

        case 38: // up arrow
          e.preventDefault()
          this.prev()
          break

        case 40: // down arrow
          e.preventDefault()
          this.next()
          break
      }

      if ((this.options.stopAdvance || (e.keyCode != 9 && e.keyCode != 13))
          && $.inArray(e.keyCode, this.options.advanceKeyCodes)) {
          e.stopPropagation()
      }
    }

  , keydown: function (e) {
      this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
      this.move(e)
    }

  , keypress: function (e) {
      if (this.suppressKeyPressRepeat) return
      this.move(e)
    }

  , keyup: function (e) {
      switch(e.keyCode) {
        case 40: // down arrow
        case 38: // up arrow
          break

        case 9: // tab
        case 13: // enter
          if (!this.shown) return
          this.select(e)
          break

        case 27: // escape
          if (!this.shown) return
          this.hide()
          if (this.on_escape) {
            this.on_escape();
          }
          break

        default:
          if (this.trigger_selection(e)) {
            if (!this.shown) return;
            this.select(e);
          }
          var hideOnEmpty = false
          if (e.keyCode === 8 && this.options.helpOnEmptyStrings) { // backspace
            hideOnEmpty = true
          }
          this.lookup(hideOnEmpty)
      }

      if ((this.options.stopAdvance || (e.keyCode != 9 && e.keyCode != 13))
          && $.inArray(e.keyCode, this.options.advanceKeyCodes)) {
          e.stopPropagation()
      }

      e.preventDefault()
  }

  , blur: function (e) {
      var that = this
      setTimeout(function () {
        if (!that.$container.is(':hover')) {
          that.hide();
        }
      }, 150)
    }

  , click: function (e) {
      e.stopPropagation()
      e.preventDefault()
      this.select(e)
    }

  , mouseenter: function (e) {
      this.$menu.find('.active').removeClass('active')
      $(e.currentTarget).addClass('active')
    }

  }

  /* TYPEAHEAD PLUGIN DEFINITION
   * =========================== */

  $.fn.typeahead = function (option) {
    return this.each(function () {
      var $this = $(this)
        , data = $this.data('typeahead')
        , options = typeof option == 'object' && option
      if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  $.fn.typeahead.defaults = {
    source: []
  , items: 8
  , container: '

'
  , header_html: '

'
  , menu: '

'
  , item: '

'
  , minLength: 1
  , stopAdvance: false
  , dropup: false
  , advanceKeyCodes: []
  }

  $.fn.typeahead.Constructor = Typeahead

 /*   TYPEAHEAD DATA-API
  * ================== */

  $(function () {
    $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
      var $this = $(this)
      if ($this.data('typeahead')) return
      e.preventDefault()
      $this.typeahead($this.data())
    })
  })

}(window.jQuery);
<|endoftext|>


<commit_before>/*

  arg.js - v1.1
  JavaScript URL argument processing once and for all.

  by Mat Ryer and Ryan Quinn
  Copyright (c) 2013 Stretchr, Inc.

  Please consider promoting this project if you find it useful.

  Permission is hereby granted, free of charge, to any person obtaining a copy of this
  software and associated documentation files (the "Software"), to deal in the Software
  without restriction, including without limitation the rights to use, copy, modify, merge,
  publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  to whom the Software is furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all copies
  or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  DEALINGS IN THE SOFTWARE.

*/

(function(global){

  /**
   * MakeArg makes the Arg namespace.
   * var Arg = MakeArg();
   */
  global.MakeArg = function(){

    /** @namespace
     */
    var Arg = function(){
      return Arg.get.apply(global, arguments);
    };
    Arg.version = "1.1.0";

    /**
     * Parses the arg string into an Arg.Arg object.
     */
    Arg.parse = function(s){
      if (!s) return {};
      if (s.indexOf("=")===-1 && s.indexOf("&")===-1) return {};
      s = Arg._cleanParamStr(s);
      var obj = {};
      var pairs = s.split("&");
      for (var pi in pairs) {
        if(pairs.hasOwnProperty(pi)){
          var kvsegs = pairs[pi].split("=");
          var key = decodeURIComponent(kvsegs[0]), val = decodeURIComponent(kvsegs[1]);
          Arg._access(obj, key, val);
        }
      }
      return obj;
    };

    /**
     * Helper method to get/set deep nested values in an object based on a string selector
     *
     * @param  {Object}  obj        Based object to either get/set selector on
     * @param  {String}  selector   Object selector ie foo[0][1].bar[0].baz.foobar
     * @param  {Mixed}   value      (optional) Value to set leaf located at `selector` to.
     *                              If value is undefined, operates in 'get' mode to return value at obj->selector
     * @return {Mixed}
     */
    Arg._access = function(obj, selector, value) {
      var shouldSet = typeof value !== "undefined";
      var selectorBreak = -1;
      var coerce_types = {
        'true'  : true,
        'false' : false,
        'null'  : null
      };

      // selector could be a number if we're at a numerical index leaf in which case selector.search is not valid
      if (typeof selector == 'string' || toString.call(selector) == '[object String]') {
        selectorBreak = selector.search(/[\.\[]/);
      }

      // No dot or array notation so we're at a leaf, set value
      if (selectorBreak === -1) {
        if (Arg.coerceMode) {
          value = value && !isNaN(value)            ? +value              // number
                : value === 'undefined'             ? undefined           // undefined
                : coerce_types[value] !== undefined ? coerce_types[value] // true, false, null
                : value;                                                  // string
        }
        return shouldSet ? (obj[selector] = value) : obj[selector];
      }

      // Example:
      // selector     = 'foo[0].bar.baz[2]'
      // currentRoot  = 'foo'
      // nextSelector = '0].bar.baz[2]' -> will be converted to '0.bar.baz[2]' in below switch statement
      var currentRoot = selector.substr(0, selectorBreak);
      var nextSelector = selector.substr(selectorBreak + 1);

      switch (selector.charAt(selectorBreak)) {
        case '[':
          // Intialize node as an array if we haven't visted it before
          obj[currentRoot] = obj[currentRoot] || [];
          nextSelector = nextSelector.replace(']', '');

          if (nextSelector.search(/[\.\[]/) === -1) {
            nextSelector = parseInt(nextSelector, 10);
          }

          return Arg._access(obj[currentRoot], nextSelector, value);
        case '.':
          // Intialize node as an object if we haven't visted it before
          obj[currentRoot] = obj[currentRoot] || {};
          return Arg._access(obj[currentRoot], nextSelector, value);
      }

      return obj;
    };

    /**
     * Turns the specified object into a URL parameter string.
     */
    Arg.stringify = function(obj, keyPrefix) {

      switch (typeof(obj)) {
      case "object":
        var segs = [];
        var thisKey;
        for (var key in obj) {

          if (!obj.hasOwnProperty(key)) continue;
          var val = obj[key];

          if (typeof(key) === "undefined" || key.length === 0 || typeof(val) === "undefined") continue;

          thisKey = keyPrefix ? keyPrefix+"."+key : key;

          if (typeof obj.length !== "undefined") {
            thisKey = keyPrefix ? keyPrefix+"["+key+"]" : key;
          }

          if (typeof val === "object") {
            segs.push(Arg.stringify(val, thisKey));
          } else {
            segs.push(encodeURIComponent(thisKey)+"="+encodeURIComponent(val));
          }

        }
        return segs.join("&");
      }

      return encodeURIComponent(obj);

    };

    /**
     * Generates a URL with the given parameters.
     * (object) = A URL to the current page with the specified parameters.
     * (path, object) = A URL to the specified path, with the object of parameters.
     * (path, object, object) = A URL to the specified path with the first object as query parameters,
     * and the second object as hash parameters.
     */
    Arg.url = function(){

      var sep = (Arg.urlUseHash ? Arg.hashQuerySeperator : Arg.querySeperator);
      var segs = [location.pathname, sep];
      var args = {};

      switch (arguments.length) {
      case 1: // Arg.url(params)
        segs.push(Arg.stringify(arguments[0]));
        break;
      case 2: // Arg.url(path, params)
        segs[0] = Arg._cleanPath(arguments[0]);
        args = Arg.parse(arguments[0]);
        args = Arg.merge(args, arguments[1]);
        segs.push(Arg.stringify(args));
        break;
      case 3: // Arg.url(path, query, hash)
        segs[0] = Arg._cleanPath(arguments[0]);
        segs[1] = Arg.querySeperator;
        segs.push(Arg.stringify(arguments[1]));
        (typeof(arguments[2])==="string") ? segs.push(Arg.hashSeperator) : segs.push(Arg.hashQuerySeperator);
        segs.push(Arg.stringify(arguments[2]));
      }

      var s = segs.join("");

      // trim off sep if it's the last thing
      if (s.indexOf(sep) == s.length - sep.length) {
        s = s.substr(0, s.length - sep.length);
      }

      return s;

    };

    /** urlUseHash tells the Arg.url method to always put the parameters in the hash. */
    Arg.urlUseHash = false;

    /** The string that seperates the path and query parameters. */
    Arg.querySeperator = "?";

    /** The string that seperates the path or query, and the hash property. */
    Arg.hashSeperator = "#";

    /** The string that seperates the the path or query, and the hash query parameters. */
    Arg.hashQuerySeperator = "#?";

    /** When parsing values if they should be coerced into primitive types, ie Number, Boolean, Undefined */
    Arg.coerceMode = true;

    /**
     * Gets all parameters from the current URL.
     */
    Arg.all = function(){
      var merged = Arg.parse(Arg.querystring() + "&" + Arg.hashstring());
      return Arg._all ? Arg._all : Arg._all = merged;
    };

    /**
     * Gets a parameter from the URL.
     */
    Arg.get = function(selector, def){
      var val = Arg._access(Arg.all(), selector);
      return typeof(val) === "undefined" ? def : val;
    };

    /**
     * Gets the query string parameters from the current URL.
     */
    Arg.query = function(){
      return Arg._query ? Arg._query : Arg._query = Arg.parse(Arg.querystring());
    };

    /**
     * Gets the hash string parameters from the current URL.
     */
    Arg.hash = function(){
      return Arg._hash ? Arg._hash : Arg._hash = Arg.parse(Arg.hashstring());
    };

    /**
     * Gets the query string from the URL (the part after the ?).
     */
    Arg.querystring = function(){
      return Arg._cleanParamStr(location.search);
    };

    /**
     * Gets the hash param string from the URL (the part after the #).
     */
    Arg.hashstring = function(){
      return Arg._cleanParamStr(location.hash)
    };

    /*
     * Cleans the URL parameter string stripping # and ? from the beginning.
     */
    Arg._cleanParamStr = function(s){

      if (s.indexOf(Arg.querySeperator)>-1)
        s = s.split(Arg.querySeperator)[1];

      if (s.indexOf(Arg.hashSeperator)>-1)
        s = s.split(Arg.hashSeperator)[1];

      if (s.indexOf("=")===-1 && s.indexOf("&")===-1)
        return "";

      while (s.indexOf(Arg.hashSeperator) == 0 || s.indexOf(Arg.querySeperator) == 0)
        s = s.substr(1);

      return s;
    };

    Arg._cleanPath = function(p){

      if (p.indexOf(Arg.querySeperator)>-1)
        p = p.substr(0,p.indexOf(Arg.querySeperator));

      if (p.indexOf(Arg.hashSeperator)>-1)
        p = p.substr(0,p.indexOf(Arg.hashSeperator));

      return p;
    };

    /**
     * Merges all the arguments into a new object.
     */
    Arg.merge = function(){
      var all = {};
      for (var ai in arguments)
        for (var k in arguments[ai])
          all[k] = arguments[ai][k];
      return all;
    };

    return Arg;

  };

  /** @namespace
   * Arg is the root namespace for all arg.js functionality.
   */
  global.Arg = MakeArg();

})(window);
<commit_msg>Added hasOwnProperty on for loop(s) :)<commit_after>/*

  arg.js - v1.1
  JavaScript URL argument processing once and for all.

  by Mat Ryer and Ryan Quinn
  Copyright (c) 2013 Stretchr, Inc.

  Please consider promoting this project if you find it useful.

  Permission is hereby granted, free of charge, to any person obtaining a copy of this
  software and associated documentation files (the "Software"), to deal in the Software
  without restriction, including without limitation the rights to use, copy, modify, merge,
  publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  to whom the Software is furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all copies
  or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  DEALINGS IN THE SOFTWARE.

*/

(function(global){

  /**
   * MakeArg makes the Arg namespace.
   * var Arg = MakeArg();
   */
  global.MakeArg = function(){

    /** @namespace
     */
    var Arg = function(){
      return Arg.get.apply(global, arguments);
    };
    Arg.version = "1.1.0";

    /**
     * Parses the arg string into an Arg.Arg object.
     */
    Arg.parse = function(s){
      if (!s) return {};
      if (s.indexOf("=")===-1 && s.indexOf("&")===-1) return {};
      s = Arg._cleanParamStr(s);
      var obj = {};
      var pairs = s.split("&");
      for (var pi in pairs) {
        if(pairs.hasOwnProperty(pi)){
          var kvsegs = pairs[pi].split("=");
          var key = decodeURIComponent(kvsegs[0]), val = decodeURIComponent(kvsegs[1]);
          Arg._access(obj, key, val);
        }
      }
      return obj;
    };

    /**
     * Helper method to get/set deep nested values in an object based on a string selector
     *
     * @param  {Object}  obj        Based object to either get/set selector on
     * @param  {String}  selector   Object selector ie foo[0][1].bar[0].baz.foobar
     * @param  {Mixed}   value      (optional) Value to set leaf located at `selector` to.
     *                              If value is undefined, operates in 'get' mode to return value at obj->selector
     * @return {Mixed}
     */
    Arg._access = function(obj, selector, value) {
      var shouldSet = typeof value !== "undefined";
      var selectorBreak = -1;
      var coerce_types = {
        'true'  : true,
        'false' : false,
        'null'  : null
      };

      // selector could be a number if we're at a numerical index leaf in which case selector.search is not valid
      if (typeof selector == 'string' || toString.call(selector) == '[object String]') {
        selectorBreak = selector.search(/[\.\[]/);
      }

      // No dot or array notation so we're at a leaf, set value
      if (selectorBreak === -1) {
        if (Arg.coerceMode) {
          value = value && !isNaN(value)            ? +value              // number
                : value === 'undefined'             ? undefined           // undefined
                : coerce_types[value] !== undefined ? coerce_types[value] // true, false, null
                : value;                                                  // string
        }
        return shouldSet ? (obj[selector] = value) : obj[selector];
      }

      // Example:
      // selector     = 'foo[0].bar.baz[2]'
      // currentRoot  = 'foo'
      // nextSelector = '0].bar.baz[2]' -> will be converted to '0.bar.baz[2]' in below switch statement
      var currentRoot = selector.substr(0, selectorBreak);
      var nextSelector = selector.substr(selectorBreak + 1);

      switch (selector.charAt(selectorBreak)) {
        case '[':
          // Intialize node as an array if we haven't visted it before
          obj[currentRoot] = obj[currentRoot] || [];
          nextSelector = nextSelector.replace(']', '');

          if (nextSelector.search(/[\.\[]/) === -1) {
            nextSelector = parseInt(nextSelector, 10);
          }

          return Arg._access(obj[currentRoot], nextSelector, value);
        case '.':
          // Intialize node as an object if we haven't visted it before
          obj[currentRoot] = obj[currentRoot] || {};
          return Arg._access(obj[currentRoot], nextSelector, value);
      }

      return obj;
    };

    /**
     * Turns the specified object into a URL parameter string.
     */
    Arg.stringify = function(obj, keyPrefix) {

      switch (typeof(obj)) {
      case "object":
        var segs = [];
        var thisKey;
        for (var key in obj) {

          if (!obj.hasOwnProperty(key)) continue;
          var val = obj[key];

          if (typeof(key) === "undefined" || key.length === 0 || typeof(val) === "undefined") continue;

          thisKey = keyPrefix ? keyPrefix+"."+key : key;

          if (typeof obj.length !== "undefined") {
            thisKey = keyPrefix ? keyPrefix+"["+key+"]" : key;
          }

          if (typeof val === "object") {
            segs.push(Arg.stringify(val, thisKey));
          } else {
            segs.push(encodeURIComponent(thisKey)+"="+encodeURIComponent(val));
          }

        }
        return segs.join("&");
      }

      return encodeURIComponent(obj);

    };

    /**
     * Generates a URL with the given parameters.
     * (object) = A URL to the current page with the specified parameters.
     * (path, object) = A URL to the specified path, with the object of parameters.
     * (path, object, object) = A URL to the specified path with the first object as query parameters,
     * and the second object as hash parameters.
     */
    Arg.url = function(){

      var sep = (Arg.urlUseHash ? Arg.hashQuerySeperator : Arg.querySeperator);
      var segs = [location.pathname, sep];
      var args = {};

      switch (arguments.length) {
      case 1: // Arg.url(params)
        segs.push(Arg.stringify(arguments[0]));
        break;
      case 2: // Arg.url(path, params)
        segs[0] = Arg._cleanPath(arguments[0]);
        args = Arg.parse(arguments[0]);
        args = Arg.merge(args, arguments[1]);
        segs.push(Arg.stringify(args));
        break;
      case 3: // Arg.url(path, query, hash)
        segs[0] = Arg._cleanPath(arguments[0]);
        segs[1] = Arg.querySeperator;
        segs.push(Arg.stringify(arguments[1]));
        (typeof(arguments[2])==="string") ? segs.push(Arg.hashSeperator) : segs.push(Arg.hashQuerySeperator);
        segs.push(Arg.stringify(arguments[2]));
      }

      var s = segs.join("");

      // trim off sep if it's the last thing
      if (s.indexOf(sep) == s.length - sep.length) {
        s = s.substr(0, s.length - sep.length);
      }

      return s;

    };

    /** urlUseHash tells the Arg.url method to always put the parameters in the hash. */
    Arg.urlUseHash = false;

    /** The string that seperates the path and query parameters. */
    Arg.querySeperator = "?";

    /** The string that seperates the path or query, and the hash property. */
    Arg.hashSeperator = "#";

    /** The string that seperates the the path or query, and the hash query parameters. */
    Arg.hashQuerySeperator = "#?";

    /** When parsing values if they should be coerced into primitive types, ie Number, Boolean, Undefined */
    Arg.coerceMode = true;

    /**
     * Gets all parameters from the current URL.
     */
    Arg.all = function(){
      var merged = Arg.parse(Arg.querystring() + "&" + Arg.hashstring());
      return Arg._all ? Arg._all : Arg._all = merged;
    };

    /**
     * Gets a parameter from the URL.
     */
    Arg.get = function(selector, def){
      var val = Arg._access(Arg.all(), selector);
      return typeof(val) === "undefined" ? def : val;
    };

    /**
     * Gets the query string parameters from the current URL.
     */
    Arg.query = function(){
      return Arg._query ? Arg._query : Arg._query = Arg.parse(Arg.querystring());
    };

    /**
     * Gets the hash string parameters from the current URL.
     */
    Arg.hash = function(){
      return Arg._hash ? Arg._hash : Arg._hash = Arg.parse(Arg.hashstring());
    };

    /**
     * Gets the query string from the URL (the part after the ?).
     */
    Arg.querystring = function(){
      return Arg._cleanParamStr(location.search);
    };

    /**
     * Gets the hash param string from the URL (the part after the #).
     */
    Arg.hashstring = function(){
      return Arg._cleanParamStr(location.hash)
    };

    /*
     * Cleans the URL parameter string stripping # and ? from the beginning.
     */
    Arg._cleanParamStr = function(s){

      if (s.indexOf(Arg.querySeperator)>-1)
        s = s.split(Arg.querySeperator)[1];

      if (s.indexOf(Arg.hashSeperator)>-1)
        s = s.split(Arg.hashSeperator)[1];

      if (s.indexOf("=")===-1 && s.indexOf("&")===-1)
        return "";

      while (s.indexOf(Arg.hashSeperator) == 0 || s.indexOf(Arg.querySeperator) == 0)
        s = s.substr(1);

      return s;
    };

    Arg._cleanPath = function(p){

      if (p.indexOf(Arg.querySeperator)>-1)
        p = p.substr(0,p.indexOf(Arg.querySeperator));

      if (p.indexOf(Arg.hashSeperator)>-1)
        p = p.substr(0,p.indexOf(Arg.hashSeperator));

      return p;
    };

    /**
     * Merges all the arguments into a new object.
     */
    Arg.merge = function(){
      var all = {};
      for (var ai in arguments){
        if(arguments.hasOwnProperty(ai)){
          for (var k in arguments[ai]){
            if(arguments[ai].hasOwnProperty(k)){
              all[k] = arguments[ai][k];
            }
          }
        }
      }
      return all;
    };

    return Arg;

  };

  /** @namespace
   * Arg is the root namespace for all arg.js functionality.
   */
  global.Arg = MakeArg();

})(window);
<|endoftext|>


<commit_before>var os = require('os')
  , path = require('path');

var p = {};

for (key in path) {
    if (typeof path[key] === 'function') {
        p[key] = path[key];
    }
}
path = p;

function Darwin() {
	this.tempdir = function() {
		return '/tmp';
	}

	this.homedir = function() {
		 return process.env['HOME'];
	}

	this.datadir = function(appname) {
		return this.homedir()+"/Library/Application Support/"+appname;
	}
}

function Linux() {
	this.tempdir = function() {
		return '/tmp';
	}

	this.homedir = function() {
		 return process.env['HOME'];
	}

	this.datadir = function(appname) {
		return this.homedir()+"/."+appname;
	}
}

function Windows() {
	this.tempdir = function() {
		return process.env['TEMP'];
	}

	this.homedir = function() {
		 return process.env['USERPROFILE'];
	}

	this.datadir = function(appname) {
		return process.env['APPDATA'];
	}
}

var ost = os.type().toLowerCase();

var OSObj = null;
   if (ost.indexOf('lin') === 0) {
        OSObj = new Linux();
    } else if (ost.indexOf('darwin') === 0) {
		OSObj = new Darwin();
    } else if (ost.indexOf('win') === 0) {
		OSObj = new Windows();
    } else {
        return null;
    }

if (path.tempdir == null) {
    path.tempdir = OSObj.tempdir;
}

if (path.homedir == null) {
    path.homedir = OSObj.homedir;
}

if (path.datadir == null) {
    path.datadir = OSObj.datadir;
}

module.exports = path;

<commit_msg>Match location used by node-webkit on linux.<commit_after>var os = require('os')
  , path = require('path');

var p = {};

for (key in path) {
    if (typeof path[key] === 'function') {
        p[key] = path[key];
    }
}
path = p;

function Darwin() {
	this.tempdir = function() {
		return '/tmp';
	}

	this.homedir = function() {
		 return process.env['HOME'];
	}

	this.datadir = function(appname) {
		return this.homedir()+"/Library/Application Support/"+appname;
	}
}

function Linux() {
	this.tempdir = function() {
		return '/tmp';
	}

	this.homedir = function() {
		 return process.env['HOME'];
	}

	this.datadir = function(appname) {
		return this.homedir()+"/.config/"+appname;
	}
}

function Windows() {
	this.tempdir = function() {
		return process.env['TEMP'];
	}

	this.homedir = function() {
		 return process.env['USERPROFILE'];
	}

	this.datadir = function(appname) {
		return process.env['APPDATA'];
	}
}

var ost = os.type().toLowerCase();

var OSObj = null;
   if (ost.indexOf('lin') === 0) {
        OSObj = new Linux();
    } else if (ost.indexOf('darwin') === 0) {
		OSObj = new Darwin();
    } else if (ost.indexOf('win') === 0) {
		OSObj = new Windows();
    } else {
        return null;
    }

if (path.tempdir == null) {
    path.tempdir = OSObj.tempdir;
}

if (path.homedir == null) {
    path.homedir = OSObj.homedir;
}

if (path.datadir == null) {
    path.datadir = OSObj.datadir;
}

module.exports = path;

<|endoftext|>


<commit_before>/**
*
* Read and process the communication data
*
**/

'use strict';

var fs = require('fs'),
    utils = require('./utils.js'),
    move = require('./move.js'),
    area = require('./area.js');
var filePrefix = '../data/comm/comm-data-',
    // TODO(bowen): temporarily disable Sat and Sun as they are too slow
    days = {'Fri': 0, 'Sat': 1, 'Sun': 2};

var origData = {};

/**
 * Data are in the form of
 * {
 *   day: {
 *     send: {
 *       pid: [ [time, pid], ..., ],
 *       ...
 *     },
 *     receive: {
 *       pid: [ [time, pid], ..., ],
 *       ...
 *     }
 *   },
 *   ...
 * }
 */
var pidData = {};

/**
 * Pids are in the form of
 * {
 *   day: {
 *     send: [ pid, pid, ... ],
 *     receive: ...,
 *   },
 *   ...
 * }
 */
var pids = {};

// Timestamp is stored as the first element in the array
var tmGeq = function(a, v) {
  return a[0] >= v;
};

var valid = function(x) {
  return (x != undefined && !isNaN(x));
};

/** @export */
module.exports = {
  setup: function() {
    var errorcnt = 0;
    for (var day in days) {
      var fileName = filePrefix + day + '.bin';
      console.log('getting', fileName);
      var buf = utils.readFileToBuffer(fileName);

      var offset = 0;
      var n = buf.readInt32LE(offset);
      offset += 4;
      var origData_day = [];

      var daySend = {},
          dayReceive = {};

      for (var i = 0; i < n; i++) {
        var tmstamp = buf.readInt32LE(offset);
        offset += 4;
        var id_from = buf.readInt16LE(offset);
        offset += 2;
        var id_to = buf.readInt16LE(offset);
        offset += 2;
        var areaCode = buf.readInt8(offset);
        offset++;

        /*
          TODO(bowen):
          origData is disabled to save memory
          as it has no use for the moment
        */
        //origData_day.push([tmstamp, id_from, id_to, areaCode]);

        // Generate two way records
        // areaCode not used
        if (daySend[id_from] == undefined) daySend[id_from] = [];
        daySend[id_from].push([tmstamp, id_to]);
        if (dayReceive[id_to] == undefined) dayReceive[id_to] = [];
        dayReceive[id_to].push([tmstamp, id_from]);

        /* //Used for checking areacode classifier
        var moveData = move.queryPidExactTime(day, id_from.toString(), tmstamp);
        for(var key in moveData) {
          var x=moveData[key][0],
              y=moveData[key][1];
          var guess = area.areaOf(x,y);
          if (guess!=areaCode){
            errorcnt++;
            //console.log('',x,',',y,',',areaCode,'not', guess);
          }
        }*/

        if (i % 200000 == 0) {
          console.log((i / n * 100).toFixed(1) + '%...');
        }
      }

      pidData[day] = {
        send: daySend,
        receive: dayReceive
      };

      pids[day] = {
        send: Object.keys(daySend),
        receive: Object.keys(dayReceive)
      };

      origData[day] = origData_day;
    }
    //console.log('mis-classified area #',errorcnt);
    console.log('comm data ready');
  },

  /**
   * Return the communication activities given a set of pids and time range.
   * @param   {string} day     'Fri' / 'Sat' / 'Sun'
   * @param   {string} direction 'send' / 'receive' / 'both'
   * @param   {string} pid     Comma separated pids
   * @param   {number} tmStart Start time
   * @param   {number} tmEnd   End time
   * @return {Object}
   *   Weighted directed graph of the communication volume between the time range
   *   The graph is described using adjacent list.
   *   For each pid, its adjacency list is in the form
   *   { id_to1: volume1, id_to2: volume2, ... }
   */
  queryPidTimeRange: function(day, direction, pid, tmStart, tmEnd) {
    if (direction == 'both') {
      var result = this.queryPidTimeRange(day, 'send', pid, tmStart, tmEnd);
      var result_r = this.queryPidTimeRange(day, 'receive', pid, tmStart, tmEnd);
      for (var id in result_r)
        for (var id2 in result_r[id]) {
          if (result[id][id2] == undefined) result[id][id2] = 0;
          result[id][id2] += result_r[id][id2];
        }
        return result;
    }

    if (pid == undefined) {
      pid = pids[day][direction];
    } else {
      if (pid == '') return {};
      pid = pid.split(',');
    }

    var result = {};
    for (var i = 0; i < pid.length; i++) {
      var id = pid[i],
          dayData = pidData[day][direction][id]; // [tmstamp, id2]
      if (dayData == undefined) continue;
      var l = 0, r = dayData.length;
      if (r == 0) continue;

      if (valid(tmStart)) l = utils.lowerBound(dayData, tmStart, tmGeq);
      if (valid(tmEnd)) r = utils.lowerBound(dayData, tmEnd + 1, tmGeq);
      if (l >= r) continue;

      result[id] = {};

      for (var j = l; j < r; j++) {
        //if (dayData[j][0] < tmStart || dayData[j][0] > tmEnd) console.log('b');
        var id2 = dayData[j][1];
        if (result[id][id2] == undefined) result[id][id2] = 0;
        result[id][id2]++;
      }
    }
    var num = 0;
    for (var pid in result)
      num += utils.size(result[pid]);
    console.log(num, 'edges');
    return result;
  },

  /**
   * Return the message send volume within a single time range.
   * Segmented queries call this repeatedly.
   * @private
   * @param     {string}  day
   * @direction {string}  direction
   * @param     {pid}     pid
   *   Single pid, pidData[day][direction][id] must exist!
   * @param     {number}  tmStart
   * @param     {number}  tmEnd
   * @return    {number}  Numeric volume
   */
  queryVolume_: function(day, direction, pid, tmStart, tmEnd) {
    if (direction == 'both') {
      var v0 = this.queryVolume_(day, 'send', pid, tmStart, tmEnd);
      var v1 = this.queryVolume_(day, 'receive', pid, tmStart, tmEnd);
      return v0 + v1;
    }
    var dayData = pidData[day][direction][pid]; // [tmstamp, id_to, areaCode]
    if (dayData == undefined)
      return 0;
    var l = 0, r = dayData.length;
    if (r == 0) return 0;
    if (valid(tmStart)) l = utils.lowerBound(dayData, tmStart, tmGeq);
    if (valid(tmEnd)) r = utils.lowerBound(dayData, tmEnd + 1, tmGeq);
    if (l >= r) return 0;
    return r - l;
  },

  /**
   * Return the message send volume for evenly segmented ranges.
   * @param {string} day
   * @param {string} direction
   * @param {pid} pid
   * @param {number} tmStart
   * @param {number} tmEnd
   * @param {number} numSeg
   */
  queryVolumeSegmented: function(day, direction, pid, tmStart, tmEnd, numSeg) {
    if (pid == undefined) {
      if (direction == 'both') {
        pid = pids[day]['send'];
        var pid_r = pids[day]['receive'];
        pid = utils.unique(pid.concat(pid_r));
      }
      else
        pid = pids[day][direction];
    } else {
      if (pid == '') return {};
      pid = pid.split(',');
    }

    var result = {};
    var tmStep = parseInt((tmEnd - tmStart + 1) / numSeg);
    if (tmStep == 0) tmStep = 1;
    for (var i = 0; i < pid.length; i++) {
      var id = pid[i];
      result[id] = [];
      for (var s = tmStart; s <= tmEnd; s += tmStep) {
        var t = Math.min(s + tmStep, tmEnd);
        var vol = this.queryVolume_(day, direction, id, s, t);
        var len = result[id].length;
        if (len >= 2 && result[id][len - 1] == vol && result[id][len - 2] == vol)
          result[id][len - 1][0] = s;
        else result[id].push([s, vol]);
      }
    }

    return result;
  },

  /**
   * Same as queryPidTimerange with tmStart = tmEnd = tmExact
   * @param {string} day
   * @param {string} pid     Comma separated pids
   * @param {number} tmExact
   * @return {Object} Same as queryPidTimeRange
   */
  queryPidExactTime: function(day, pid, tmExact) {
    return this.queryPidTimeRange(day, pid, tmExact, tmExact);
  },

  /**
   * Volume sequence for each pid
   * @param {string} day
   * @param {string} direction
   * @param {string} pid Comma separated pids
   */
  queryVolumeSequence: function(day, direction, pid) {
    if (pid == undefined) {
      if (direction == 'both') {
        pid = pids[day]['send'];
        var pid_r = pids[day]['receive'];
        pid = utils.unique(pid.concat(pid_r));
      }
      else
        pid = pids[day][direction];
    } else {
      if (pid == '') return {};
      pid = pid.split(',');
    }
    var result = {};
    var count = 0;
    for (var i = 0; i < pid.length; i++) {
      var id = pid[i],
          dayData;

      if (direction == 'both') {
        var send_data = pidData[day]['send'][id];
        var recv_data = pidData[day]['receive'][id];
        dayData = utils.merge_2(send_data, recv_data);
      }
      else
        dayData = pidData[day][direction][id];
      if (dayData == undefined) continue;
      var seq = [], lastt = -1;
      for (var j = 0; j < dayData.length; j++) {
        var t = parseInt(dayData[j][0]);
        if (t != lastt) {
          seq.push([t, 0]);
          lastt = t;
        }
        seq[seq.length - 1][1] ++;
      }
      for (var j = 1; j < seq.length; j++) {
        seq[j][1] += seq[j - 1][1];
      }
      result[id] = seq;
      count += seq.length;
    }
    console.log(count, 'elements returned');
    return result;
  }

};
<commit_msg>group comm<commit_after>/**
*
* Read and process the communication data
*
**/

'use strict';

var fs = require('fs'),
    utils = require('./utils.js'),
    move = require('./move.js'),
    group = require('./group.js'),
    area = require('./area.js');
var filePrefix = '../data/comm/comm-data-',
    days = {'Fri': 0, 'Sat': 1, 'Sun': 2};

var groupInfo,
    origData = {};

/**
 * Data are in the form of
 * {
 *   day: {
 *     send: {
 *       pid: [ [time, pid], ..., ],
 *       ...
 *     },
 *     receive: {
 *       pid: [ [time, pid], ..., ],
 *       ...
 *     }
 *   },
 *   ...
 * }
 */
var pidData = {};

/**
 * Pids are in the form of
 * {
 *   day: {
 *     send: [ pid, pid, ... ],
 *     receive: ...,
 *   },
 *   ...
 * }
 */
var pids = {};

// Timestamp is stored as the first element in the array
var tmGeq = function(a, v) {
  return a[0] >= v;
};

var valid = function(x) {
  return (x != undefined && !isNaN(x));
};

/** @export */
module.exports = {
  setup: function() {
    var errorcnt = 0;
    groupInfo = group.allGroupInfo();
    for (var day in days) {
      var fileName = filePrefix + day + '.bin';
      console.log('getting', fileName);
      var buf = utils.readFileToBuffer(fileName);

      var offset = 0;
      var n = buf.readInt32LE(offset);
      offset += 4;
      var origData_day = [];

      var daySend = {},
          dayReceive = {};

      for (var i = 0; i < n; i++) {
        var tmstamp = buf.readInt32LE(offset);
        offset += 4;
        var id_from = buf.readInt16LE(offset);
        offset += 2;
        var id_to = buf.readInt16LE(offset);
        offset += 2;
        var areaCode = buf.readInt8(offset);
        offset++;

        /*
          TODO(bowen):
          origData is disabled to save memory
          as it has no use for the moment
        */
        //origData_day.push([tmstamp, id_from, id_to, areaCode]);

        // Generate two way records
        // areaCode not used
        if (daySend[id_from] == undefined) daySend[id_from] = [];
        daySend[id_from].push([tmstamp, id_to]);
        if (dayReceive[id_to] == undefined) dayReceive[id_to] = [];
        dayReceive[id_to].push([tmstamp, id_from]);

        var gid_from = group.GID_OFFSET + groupInfo.in_group[day][id_from];
        var gid_to = group.GID_OFFSET + groupInfo.in_group[day][id_to];

        if (daySend[gid_from] == undefined) daySend[gid_from] = [];
        daySend[gid_from].push([tmstamp, id_to]);
        if (dayReceive[gid_to] == undefined) dayReceive[gid_to] = [];
        dayReceive[gid_to].push([tmstamp, id_from]);

        /* //Used for checking areacode classifier
        var moveData = move.queryPidExactTime(day, id_from.toString(), tmstamp);
        for(var key in moveData) {
          var x=moveData[key][0],
              y=moveData[key][1];
          var guess = area.areaOf(x,y);
          if (guess!=areaCode){
            errorcnt++;
            //console.log('',x,',',y,',',areaCode,'not', guess);
          }
        }*/

        if (i % 200000 == 0) {
          console.log((i / n * 100).toFixed(1) + '%...');
        }
      }

      pidData[day] = {
        send: daySend,
        receive: dayReceive
      };

      pids[day] = {
        send: Object.keys(daySend),
        receive: Object.keys(dayReceive)
      };

      origData[day] = origData_day;
    }
    //console.log('mis-classified area #',errorcnt);
    console.log('comm data ready');
  },

  /**
   * Return the communication activities given a set of pids and time range.
   * @param   {string} day     'Fri' / 'Sat' / 'Sun'
   * @param   {string} direction 'send' / 'receive' / 'both'
   * @param   {string} pid     Comma separated pids
   * @param   {number} tmStart Start time
   * @param   {number} tmEnd   End time
   * @return {Object}
   *   Weighted directed graph of the communication volume between the time range
   *   The graph is described using adjacent list.
   *   For each pid, its adjacency list is in the form
   *   { id_to1: volume1, id_to2: volume2, ... }
   */
  queryPidTimeRange: function(day, direction, pid, tmStart, tmEnd) {
    if (direction == 'both') {
      var result = this.queryPidTimeRange(day, 'send', pid, tmStart, tmEnd);
      var result_r = this.queryPidTimeRange(day, 'receive', pid, tmStart, tmEnd);
      for (var id in result_r)
        for (var id2 in result_r[id]) {
          if (result[id][id2] == undefined) result[id][id2] = 0;
          result[id][id2] += result_r[id][id2];
        }
        return result;
    }

    if (pid == undefined) {
      pid = pids[day][direction];
      var gcnt = 0;
      for (var i = pid.length - 1; i >= 0 && pid[i] >= group.GID_OFFSET; i--, gcnt++);
      pid = pid.slice(-gcnt);
    } else {
      if (pid == '') return {};
      pid = pid.split(',');
    }

    var expanded = new Set();

    for (var i = 0; i < pid.length; i++) {
      var id = pid[i],
          gid = group.GID_OFFSET + groupInfo.in_group[day][id];
      if (id < group.GID_OFFSET) expanded.add(gid);
    }

    var result = {};
    for (var i = 0; i < pid.length; i++) {
      var id = pid[i],
          dayData = pidData[day][direction][id]; // [tmstamp, id2]
      if (dayData == undefined) continue;
      var l = 0, r = dayData.length;
      if (r == 0) continue;

      if (valid(tmStart)) l = utils.lowerBound(dayData, tmStart, tmGeq);
      if (valid(tmEnd)) r = utils.lowerBound(dayData, tmEnd + 1, tmGeq);
      if (l >= r) continue;

      result[id] = {};

      for (var j = l; j < r; j++) {
        //if (dayData[j][0] < tmStart || dayData[j][0] > tmEnd) console.log('b');
        var id2 = dayData[j][1];
        var gid2 = group.GID_OFFSET + groupInfo.in_group[day][id2];
        if (expanded.has(gid2)) {
          if (result[id][id2] == undefined) result[id][id2] = 0;
          result[id][id2]++;
        }
        else {
          if (result[id][gid2] == undefined) result[id][gid2] = 0;
          result[id][gid2]++;
        }
      }
    }
    var num = 0;
    for (var pid in result)
      num += utils.size(result[pid]);
    console.log(num, 'edges');
    return result;
  },

  /**
   * Return the message send volume within a single time range.
   * Segmented queries call this repeatedly.
   * @private
   * @param     {string}  day
   * @direction {string}  direction
   * @param     {pid}     pid
   *   Single pid, pidData[day][direction][id] must exist!
   * @param     {number}  tmStart
   * @param     {number}  tmEnd
   * @return    {number}  Numeric volume
   */
  queryVolume_: function(day, direction, pid, tmStart, tmEnd) {
    if (direction == 'both') {
      var v0 = this.queryVolume_(day, 'send', pid, tmStart, tmEnd);
      var v1 = this.queryVolume_(day, 'receive', pid, tmStart, tmEnd);
      return v0 + v1;
    }
    var dayData = pidData[day][direction][pid]; // [tmstamp, id_to, areaCode]
    if (dayData == undefined)
      return 0;
    var l = 0, r = dayData.length;
    if (r == 0) return 0;
    if (valid(tmStart)) l = utils.lowerBound(dayData, tmStart, tmGeq);
    if (valid(tmEnd)) r = utils.lowerBound(dayData, tmEnd + 1, tmGeq);
    if (l >= r) return 0;
    return r - l;
  },

  /**
   * Return the message send volume for evenly segmented ranges.
   * @param {string} day
   * @param {string} direction
   * @param {pid} pid
   * @param {number} tmStart
   * @param {number} tmEnd
   * @param {number} numSeg
   */
  queryVolumeSegmented: function(day, direction, pid, tmStart, tmEnd, numSeg) {
    if (pid == undefined) {
      if (direction == 'both') {
        pid = pids[day]['send'];
        var pid_r = pids[day]['receive'];
        pid = utils.unique(pid.concat(pid_r));
      }
      else
        pid = pids[day][direction];
      var gcnt = 0;
      for (var i = pid.length - 1; i >= 0 && pid[i] >= group.GID_OFFSET; i--, gcnt++);
      pid = pid.slice(-gcnt);
    } else {
      if (pid == '') return {};
      pid = pid.split(',');
    }

    var result = {};
    var tmStep = parseInt((tmEnd - tmStart + 1) / numSeg);
    if (tmStep == 0) tmStep = 1;
    for (var i = 0; i < pid.length; i++) {
      var id = pid[i];
      result[id] = [];
      for (var s = tmStart; s <= tmEnd; s += tmStep) {
        var t = Math.min(s + tmStep, tmEnd);
        var vol = this.queryVolume_(day, direction, id, s, t);
        var len = result[id].length;
        if (len >= 2 && result[id][len - 1] == vol && result[id][len - 2] == vol)
          result[id][len - 1][0] = s;
        else result[id].push([s, vol]);
      }
    }

    return result;
  },

  /**
   * Same as queryPidTimerange with tmStart = tmEnd = tmExact
   * @param {string} day
   * @param {string} pid     Comma separated pids
   * @param {number} tmExact
   * @return {Object} Same as queryPidTimeRange
   */
  queryPidExactTime: function(day, pid, tmExact) {
    return this.queryPidTimeRange(day, pid, tmExact, tmExact);
  },

  /**
   * Volume sequence for each pid
   * @param {string} day
   * @param {string} direction
   * @param {string} pid Comma separated pids
   */
  queryVolumeSequence: function(day, direction, pid) {
    if (pid == undefined) {
      if (direction == 'both') {
        pid = pids[day]['send'];
        var pid_r = pids[day]['receive'];
        pid = utils.unique(pid.concat(pid_r));
      }
      else
        pid = pids[day][direction];
      var gcnt = 0;
      for (var i = pid.length - 1; i >= 0 && pid[i] >= group.GID_OFFSET; i--, gcnt++);
      pid = pid.slice(-gcnt);
    } else {
      if (pid == '') return {};
      pid = pid.split(',');
    }
    var result = {};
    var count = 0;
    for (var i = 0; i < pid.length; i++) {
      var id = pid[i],
          dayData;

      if (direction == 'both') {
        var send_data = pidData[day]['send'][id];
        var recv_data = pidData[day]['receive'][id];
        dayData = utils.merge_2(send_data, recv_data);
      }
      else
        dayData = pidData[day][direction][id];
      if (dayData == undefined) continue;
      var seq = [], lastt = -1;
      for (var j = 0; j < dayData.length; j++) {
        var t = parseInt(dayData[j][0]);
        if (t != lastt) {
          seq.push([t, 0]);
          lastt = t;
        }
        seq[seq.length - 1][1] ++;
      }
      for (var j = 1; j < seq.length; j++) {
        seq[j][1] += seq[j - 1][1];
      }
      result[id] = seq;
      count += seq.length;
    }
    console.log(count, 'elements returned');
    return result;
  }

};
<|endoftext|>