!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.eve=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o} Returns a promise resolving with the response message */ RequestModule.prototype.request = function (to, message) { var me = this; return new Promise(function (resolve, reject) { // put the data in an envelope with id var id = uuid(); var envelope = { type: 'request', id: id, message: message }; // add the request to the list with requests in progress me.queue[id] = { resolve: resolve, reject: reject, timeout: setTimeout(function () { delete me.queue[id]; reject(new Error('Timeout')); }, me.timeout) }; me.agent.send(to, envelope) .catch(function (err) { reject(err); }); }); }; /** * Get a map with mixin functions * @return {{_receive: function, request: function}} * Returns mixin function, which can be used to extend the agent. */ RequestModule.prototype.mixin = function () { return { _receive: this.receive.bind(this), request: this.request.bind(this) } }; module.exports = RequestModule; },{"../util":23,"promise":114,"uuid-v4":125}],9:[function(_dereq_,module,exports){ 'use strict'; var Promise = _dereq_('promise'); /** * An abstract Transport connection * @param {Transport} transport * @param {string} id * @param {function} receive * @constructor * @abstract */ function Connection (transport, id, receive) { throw new Error('Cannot create an abstract Connection'); } Connection.prototype.ready = Promise.reject(new Error('Cannot get abstract property ready')); /** * Send a message to an agent. * @param {string} to * @param {*} message * @return {Promise} returns a promise which resolves when the message has been sent */ Connection.prototype.send = function (to, message) { throw new Error('Cannot call abstract function send'); }; /** * Close the connection, disconnect from the transport. */ Connection.prototype.close = function () { throw new Error('Cannot call abstract function "close"'); }; module.exports = Connection; },{"promise":114}],10:[function(_dereq_,module,exports){ 'use strict'; /** * Abstract prototype of a transport * @param {Object} [config] * @constructor */ function Transport(config) { this.id = config && config.id || null; this['default'] = config && config['default'] || false; } Transport.prototype.type = null; /** * Connect an agent * @param {String} id * @param {Function} receive Invoked as receive(from, message) * @return {Connection} Returns a connection */ Transport.prototype.connect = function(id, receive) { throw new Error('Cannot invoke abstract function "connect"'); }; /** * Close the transport */ Transport.prototype.close = function() { throw new Error('Cannot invoke abstract function "close"'); }; module.exports = Transport; },{}],11:[function(_dereq_,module,exports){ 'use strict'; var Promise = _dereq_('promise'); var Connection = _dereq_('../Connection'); /** * A local connection. * @param {AMQPTransport} transport * @param {string | number} id * @param {function} receive * @constructor */ function AMQPConnection(transport, id, receive) { this.transport = transport; this.id = id; // ready state this.ready = this.transport._connect(id, receive); } /** * Send a message to an agent. * @param {string} to * @param {*} message * @return {Promise} returns a promise which resolves when the message has been sent */ AMQPConnection.prototype.send = function (to, message) { var me = this; return new Promise(function (resolve, reject) { var msg = { body: { from: me.id, to: to, message: message } }; var options = { //immediate: true }; me.transport.exchange.publish(to, msg, options, function () { // FIXME: callback is not called. //console.log('sent', arguments) }); resolve(); }); }; /** * Close the connection */ AMQPConnection.prototype.close = function () { this.transport._close(this.id); }; module.exports = AMQPConnection; },{"../Connection":9,"promise":114}],12:[function(_dereq_,module,exports){ 'use strict'; var Promise = _dereq_('promise'); var Transport = _dereq_('./../Transport'); var AMQPConnection = _dereq_('./AMQPConnection'); /** * Use AMQP as transport * @param {Object} config Config can contain the following properties: * - `id: string` * - `url: string` * - `host: string` * The config must contain either `url` or `host`. * For example: {url: 'amqp://localhost'} or * {host: 'dev.rabbitmq.com'} * @constructor */ function AMQPTransport(config) { this.id = config.id || null; this.networkId = config.url || config.host || null; this['default'] = config['default'] || false; this.config = config; this.connection = null; this.exchange = null; this.subscriptions = []; } AMQPTransport.prototype = new Transport(); AMQPTransport.prototype.type = 'amqp'; /** * Connect an agent * @param {String} id * @param {Function} receive Invoked as receive(from, message) * @return {AMQPConnection} Returns a connection. */ AMQPTransport.prototype.connect = function(id, receive) { return new AMQPConnection(this, id, receive); }; /** * Get an AMQP connection. If there is not yet a connection, a connection will * be made. * @param {Function} callback Invoked as callback(connection) * @private */ AMQPTransport.prototype._getConnection = function(callback) { var me = this; if (this.connection) { // connection is available callback(this.connection); } else { if (this._onConnected) { // connection is being opened but not yet ready this._onConnected.push(callback); } else { // no connection, create one this._onConnected = [callback]; var amqp = _dereq_('amqp'); // lazy load the amqp library var connection = amqp.createConnection(this.config); connection.on('ready', function () { var exchange = connection.exchange('', {confirm: true}, function () { var _onConnected = me._onConnected; delete me._onConnected; me.connection = connection; me.exchange = exchange; _onConnected.forEach(function (callback) { callback(me.connection); }); }); }); } } }; /** * Open a connection * @param {string} id * @param {Function} receive Invoked as receive(from, message) */ AMQPTransport.prototype._connect = function(id, receive) { var me = this; return new Promise(function (resolve, reject) { function subscribe(connection) { var queue = connection.queue(id, {}, function() { queue .subscribe(function(message) { var body = message.body; receive(body.from, body.message); }) .addCallback(function (ok) { // register this subscription me.subscriptions.push({ id: id, consumerTag: ok.consumerTag }); resolve(me); }); }); } me._getConnection(subscribe); }); }; /** * Close a connection an agent by its id * @param {String} id */ AMQPTransport.prototype._close = function(id) { var i = 0; while (i < this.subscriptions.length) { var subscription = this.subscriptions[i]; if (subscription.id == id) { // remove this entry this.subscriptions.splice(i, 1); } else { i++; } } if (this.subscriptions.length == 0) { // fully disconnect if there are no subscribers left this.exchange.destroy(); this.connection.disconnect(); this.connection = null; this.exchange = null; } }; /** * Close the transport. */ AMQPTransport.prototype.close = function() { this.connection.destroy(); this.connection = null; }; module.exports = AMQPTransport; },{"./../Transport":10,"./AMQPConnection":11,"amqp":24,"promise":114}],13:[function(_dereq_,module,exports){ 'use strict'; var Promise = _dereq_('promise'); var Connection = _dereq_('../Connection'); /** * A local connection. * @param {DistribusTransport} transport * @param {string | number} id * @param {function} receive * @constructor */ function DistribusConnection(transport, id, receive) { this.transport = transport; this.id = id; // create a peer var peer = this.transport.host.create(id); peer.on('message', receive); // ready state this.ready = Promise.resolve(this); } /** * Send a message to an agent. * @param {string} to * @param {*} message * @return {Promise} returns a promise which resolves when the message has been sent */ DistribusConnection.prototype.send = function (to, message) { return this.transport.host.send(this.id, to, message); }; /** * Close the connection */ DistribusConnection.prototype.close = function () { this.transport.host.remove(this.id); }; module.exports = DistribusConnection; },{"../Connection":9,"promise":114}],14:[function(_dereq_,module,exports){ 'use strict'; var distribus = _dereq_('distribus'); var Transport = _dereq_('./../Transport'); var DistribusConnection = _dereq_('./DistribusConnection'); /** * Use distribus as transport * @param {Object} config Config can contain the following properties: * - `id: string`. Optional * - `host: distribus.Host`. Optional * If `host` is not provided, * a new local distribus Host is created. * @constructor */ function DistribusTransport(config) { this.id = config && config.id || null; this['default'] = config && config['default'] || false; this.host = config && config.host || new distribus.Host(config); this.networkId = this.host.networkId; // FIXME: networkId can change when host connects to another host. } DistribusTransport.prototype = new Transport(); DistribusTransport.prototype.type = 'distribus'; /** * Connect an agent * @param {String} id * @param {Function} receive Invoked as receive(from, message) * @return {DistribusConnection} Returns a connection. */ DistribusTransport.prototype.connect = function(id, receive) { return new DistribusConnection(this, id, receive); }; /** * Close the transport. */ DistribusTransport.prototype.close = function() { this.host.close(); this.host = null; }; module.exports = DistribusTransport; },{"./../Transport":10,"./DistribusConnection":13,"distribus":67}],15:[function(_dereq_,module,exports){ 'use strict'; var Promise = _dereq_('promise'); var Connection = _dereq_('../Connection'); /** * A HTTP connection. * @param {HTTPTransport} transport * @param {string | number} id * @param {function} receive * @constructor */ function HTTPConnection(transport, id, receive) { this.transport = transport; this.id = id; // register the agents receive function if (this.id in this.transport.agents) { throw new Error('Agent with id ' + id + ' already exists'); } this.transport.agents[this.id] = receive; // ready state this.ready = Promise.resolve(this); } /** * Send a message to an agent. * @param {string} to * @param {*} message */ HTTPConnection.prototype.send = function (to, message) { var fromURL = this.transport.url.replace(':id', this.id); var isURL = to.indexOf('://') !== -1; var toURL; if (isURL) { toURL = to; } else { if (this.transport.remoteUrl !== undefined) { toURL = this.transport.remoteUrl.replace(':id', to); } else { console.log('ERROR: no remote URL specified. Cannot send over HTTP.', to); } } return this.transport.send(fromURL, toURL, message); }; /** * Close the connection */ HTTPConnection.prototype.close = function () { delete this.transport.agents[this.id]; }; module.exports = HTTPConnection; },{"../Connection":9,"promise":114}],16:[function(_dereq_,module,exports){ 'use strict'; var http = _dereq_('http'); var Promise = _dereq_('promise'); var Transport = _dereq_('./../Transport'); var HTTPConnection = _dereq_('./HTTPConnection'); var uuid = _dereq_('uuid-v4'); /** * HTTP Transport layer: * * Supported Options: * * {Number} config.port Port to listen on. * {String} config.path Path, with or without leading and trailing slash (/) * {Boolean} config.localShortcut If the agentId exists locally, use local transport. (local) * * Address: http://127.0.0.1:PORTNUMBER/PATH */ function HTTPTransport(config) { this.id = config && config.id || null; this.networkId = null; this.agents = {}; this.outstandingRequests = {}; // these are received messages that are expecting a response this.outstandingMessages = {}; this.url = config && config.url || "http://127.0.0.1:3000/agents/:id"; this.remoteUrl = config && config.remoteUrl; this.localShortcut = (config && config.localShortcut === false) ? false : true; this.httpTimeout = config && config.httpTimeout || 2000; // 1 second - timeout to send message this.httpResponseTimeout = config && config.httpResponseTimeout || 200; // 0.5 second - timeout to expect reply after delivering request this.regexHosts = /[http]{4}s?:\/\/([a-z\-\.A-Z0-9]*):?([0-9]*)(\/[a-z\/:A-Z0-9._\-% \\\(\)\*\+\.\^\$]*)/; this.urlHostData = this.regexHosts.exec(this.url); this.regexPath = this.getRegEx(this.urlHostData[3]); this.port = config && config.port || this.urlHostData[2] || 3000; this.path = this.urlHostData[3].replace(':id', ''); } HTTPTransport.prototype = new Transport(); HTTPTransport.prototype.type = 'http'; HTTPTransport.prototype.getRegEx = function(url) { return new RegExp(url.replace(/[\\\(\)\*\+\.\^\$]/g,function(match) {return '\\' + match;}).replace(':id','([:a-zA-Z_0-9]*)')); }; function askAgent(url,method,params,callback, async) { if (async === undefined) { async = true; } // create post request var POSTrequest = JSON.stringify({"id":0, "method": method, "params": params}); // create XMLHttpRequest object to send the POST request var http = new XMLHttpRequest(); // insert the callback function. This is called when the message has been delivered and a response has been received http.onreadystatechange = function () { if (http.readyState == 4 && http.status == 200) { if (callback === undefined || callback === null) { } else { // launch callback function callback(JSON.parse(http.responseText)); } } else if (http.readyState == 4 && http.status != 200) { console.log("Make sure that the Node server has started."); } }; // open an asynchronous POST connection http.open("POST", url, async); // include header so the receiving code knows its a JSON object http.setRequestHeader("Content-type", "application/json"); // send http.send(POSTrequest); } /** * Connect an agent * @param {String} id * @param {Function} receive Invoked as receive(from, message) * @return {HTTPConnection} Returns a connection. */ HTTPTransport.prototype.connect = function(id, receive) { if (this.server === undefined) { this.initiateServer(); } this.outstandingRequests[id] = {}; this.outstandingMessages[id] = {}; return new HTTPConnection(this, id, receive); }; /** * Send a message to an agent * @param {String} from Id of sender * @param {String} to Id of addressed peer * @param {String} message */ HTTPTransport.prototype.send = function(from, to, message) { var me = this; return new Promise(function (resolve,reject) { var hostData = me.regexHosts.exec(to); var fromRegexpCheck = me.regexPath.exec(from); var fromAgentId = fromRegexpCheck[1]; var outstandingMessageID = uuid(); // check for local shortcut possibility if (me.localShortcut == true) { var toRegexpCheck = me.regexPath.exec(to); var toAgentId = toRegexpCheck[1]; var toPath = hostData[3].replace(toAgentId,""); // check if the "to" address is on the same URL, port and path as the "from" if ((hostData[1] == '127.0.0.1' && hostData[2] == me.urlHostData[2] && toPath == me.path) || (me.urlHostData[1] == hostData[1] && hostData[2] == me.urlHostData[2] && toPath == me.path)) { // by definition true but check anyway if (me.agents[toAgentId] !== undefined) { me.agents[toAgentId](fromAgentId, message); resolve(); return; } } } // stringify the message. If the message is an object, it can have an ID so it may be part of a req/rep. if (typeof message == 'object') { // check if the send is a reply to an outstanding request and if so, deliver var outstanding = me.outstandingRequests[fromAgentId]; if (outstanding[message.id] !== undefined) { var callback = outstanding[message.id]; callback.response.end(JSON.stringify(message)); clearTimeout(callback.timeout); delete outstanding[message.id]; resolve(); return; } // stringify the message. message = JSON.stringify(message) } // all post options var options = { host: hostData[1], port: hostData[2], path: hostData[3], method: 'POST', headers: { 'x-eve-senderurl' : from, // used to get senderID 'Content-type' : 'text/plain' } }; var request = http.request(options, function(res) { res.setEncoding('utf8'); // message was delivered, clear the cannot deliver timeout. clearTimeout(me.outstandingMessages[fromAgentId][outstandingMessageID].timeout); // listen to incoming data res.on('data', function (response) { var parsedResponse; try {parsedResponse = JSON.parse(response);} catch (err) {parsedResponse = response;} if (typeof parsedResponse == 'object') { if (parsedResponse.__httpError__ !== undefined) { reject(new Error(parsedResponse.__httpError__)); return; } } me.agents[fromAgentId](to, parsedResponse); resolve(); }); }); me.outstandingMessages[fromAgentId][outstandingMessageID] = { timeout: setTimeout(function () { request.abort(); reject(new Error("Cannot connect to " + to)) }, me.httpTimeout), reject: reject }; request.on('error', function(e) { reject(e); }); // write data to request body request.write(message); request.end(); }); }; /** * This is the HTTP equivalent of receiveMessage. * * @param request * @param response */ HTTPTransport.prototype.processRequest = function(request, response) { var url = request.url; // define headers var headers = {}; headers['Access-Control-Allow-Origin'] = '*'; headers['Access-Control-Allow-Credentials'] = true; headers['Content-Type'] = 'text/plain'; var regexpCheck = this.regexPath.exec(url); if (regexpCheck !== null) { var agentId = regexpCheck[1]; var senderId = 'unknown'; if (request.headers['x-eve-senderurl'] !== undefined) { senderId = request.headers['x-eve-senderurl']; } var body = ''; request.on('data', function (data) { body += data; if (body.length > 1e6) { // 1e6 == 1MB request.connection.destroy(); // FLOOD ATTACK OR FAULTY CLIENT, NUKE REQUEST } }); var me = this; request.on('end', function () { var expectReply = false; var message; try {message = JSON.parse(body);} catch (err) {message = body;} // check if JSON RPC expectReply = message.jsonrpc && message.jsonrpc == '2.0' || expectReply; // check if type == 'request' expectReply = message.type && message.type == 'request' || expectReply; response.writeHead(200, headers); // construct callback var callback = me.agents[agentId]; if (callback === undefined) { var error = new Error('Agent: "' + agentId + '" does not exist.'); response.end(JSON.stringify({__httpError__:error.message || error.toString()})); } else { if (expectReply == true) { me.outstandingRequests[agentId][message.id] = { response: response, timeout: setTimeout(function () { response.end("timeout"); delete me.outstandingRequests[agentId][message.id]; }, me.httpResponseTimeout) }; callback(senderId, message); } else { // if we're not expecting a response, we first close the connection, then receive the message response.end(''); if (callback !== undefined) { callback(senderId, message); } } } }); } }; /** * Configure a HTTP server listener */ HTTPTransport.prototype.initiateServer = function() { if (this.server === undefined) { var me = this; this.server = http.createServer(function (request, response) { if (request.method == 'OPTIONS') { var headers = {}; headers['Access-Control-Allow-Origin'] = '*'; headers['Access-Control-Allow-Methods'] = 'POST, OPTIONS'; headers['Access-Control-Allow-Credentials'] = true; headers['Access-Control-Max-Age'] = '86400'; // 24 hours headers['Access-Control-Allow-Headers'] = 'X-Requested-With, Access-Control-Allow-Origin, X-HTTP-Method-Override, Content-Type, Authorization, Accept'; // respond to the request response.writeHead(200, headers); response.end(); } else if (request.method == 'POST') { me.processRequest(request, response); } }); this.server.on('error', function(err) { if (err.code == 'EADDRINUSE') { throw new Error('ERROR: Could not start HTTP server. Port ' + me.port + ' is occupied.'); } else { throw new Error(err); } }); // Listen on port (default: 3000), IP defaults to 127.0.0.1 this.server.listen(this.port, function() { // Put a friendly message on the terminal console.log('Server listening at ', me.url); }); } else { this.server.close(); this.server = undefined; this.initiateServer(); } }; /** * Close the HTTP server */ HTTPTransport.prototype.close = function() { // close all open connections for (var agentId in this.outstandingRequests) { if (this.outstandingRequests.hasOwnProperty(agentId)) { var agentRequests = this.outstandingRequests[agentId]; for (var messageId in agentRequests) { if (agentRequests.hasOwnProperty(messageId)) { var openMessage = agentRequests[messageId]; var error = new Error('Server shutting down.'); openMessage.response.end(JSON.stringify({__httpError__:error.message || error.toString()})); } } } } // close server if (this.server) { this.server.close(); } this.server = null; }; module.exports = HTTPTransport; },{"./../Transport":10,"./HTTPConnection":15,"http":138,"promise":114,"uuid-v4":125}],17:[function(_dereq_,module,exports){ 'use strict'; var Promise = _dereq_('promise'); var Connection = _dereq_('../Connection'); /** * A local connection. * @param {LocalTransport} transport * @param {string | number} id * @param {function} receive * @constructor */ function LocalConnection(transport, id, receive) { this.transport = transport; this.id = id; // register the agents receive function if (this.id in this.transport.agents) { throw new Error('Agent with id ' + id + ' already exists'); } this.transport.agents[this.id] = receive; // ready state this.ready = Promise.resolve(this); } /** * Send a message to an agent. * @param {string} to * @param {*} message * @return {Promise} returns a promise which resolves when the message has been sent */ LocalConnection.prototype.send = function (to, message) { var callback = this.transport.agents[to]; if (!callback) { throw new Error('Agent with id ' + to + ' not found'); } // invoke the agents receiver as callback(from, message) callback(this.id, message); return Promise.resolve(); }; /** * Close the connection */ LocalConnection.prototype.close = function () { delete this.transport.agents[this.id]; }; module.exports = LocalConnection; },{"../Connection":9,"promise":114}],18:[function(_dereq_,module,exports){ 'use strict'; var Transport = _dereq_('./../Transport'); var LocalConnection = _dereq_('./LocalConnection'); /** * Create a local transport. * @param {Object} config Config can contain the following properties: * - `id: string`. Optional * @constructor */ function LocalTransport(config) { this.id = config && config.id || null; this.networkId = this.id || null; this['default'] = config && config['default'] || false; this.agents = {}; } LocalTransport.prototype = new Transport(); LocalTransport.prototype.type = 'local'; /** * Connect an agent * @param {String} id * @param {Function} receive Invoked as receive(from, message) * @return {LocalConnection} Returns a promise which resolves when * connected. */ LocalTransport.prototype.connect = function(id, receive) { return new LocalConnection(this, id, receive); }; /** * Close the transport. Removes all agent connections. */ LocalTransport.prototype.close = function() { this.agents = {}; }; module.exports = LocalTransport; },{"./../Transport":10,"./LocalConnection":17}],19:[function(_dereq_,module,exports){ 'use strict'; var Promise = _dereq_('promise'); var Connection = _dereq_('../Connection'); /** * A connection. The connection is ready when the property .ready resolves. * @param {PubNubTransport} transport * @param {string | number} id * @param {function} receive * @constructor */ function PubNubConnection(transport, id, receive) { this.id = id; this.transport = transport; // ready state var me = this; this.ready = new Promise(function (resolve, reject) { transport.pubnub.subscribe({ channel: id, message: function (message) { receive(message.from, message.message); }, connect: function () { resolve(me); } }); }); } /** * Send a message to an agent. * @param {string} to * @param {*} message * @return {Promise} returns a promise which resolves when the message has been sent */ PubNubConnection.prototype.send = function (to, message) { var me = this; return new Promise(function (resolve, reject) { me.transport.pubnub.publish({ channel: to, message: { from: me.id, to: to, message: message }, callback: resolve, error: reject }); }); }; /** * Close the connection */ PubNubConnection.prototype.close = function () { this.transport.pubnub.unsubscribe({ channel: this.id }); }; module.exports = PubNubConnection; },{"../Connection":9,"promise":114}],20:[function(_dereq_,module,exports){ 'use strict'; var Transport = _dereq_('./../Transport'); var PubNubConnection = _dereq_('./PubNubConnection'); /** * Use pubnub as transport * @param {Object} config Config can contain the following properties: * - `id: string`. Optional * - `publish_key: string`. Required * - `subscribe_key: string`. Required * @constructor */ function PubNubTransport(config) { this.id = config.id || null; this.networkId = config.publish_key || null; this['default'] = config['default'] || false; this.pubnub = PUBNUB().init(config); } PubNubTransport.prototype = new Transport(); PubNubTransport.prototype.type = 'pubnub'; /** * Connect an agent * @param {String} id * @param {Function} receive Invoked as receive(from, message) * @return {PubNubConnection} Returns a connection */ PubNubTransport.prototype.connect = function(id, receive) { return new PubNubConnection(this, id, receive) }; /** * Close the transport. */ PubNubTransport.prototype.close = function() { // FIXME: how to correctly close a pubnub connection? this.pubnub = null; }; /** * Load the PubNub library * @returns {Object} PUBNUB */ function PUBNUB() { if (typeof window !== 'undefined') { // browser if (typeof window['PUBNUB'] === 'undefined') { throw new Error('Please load pubnub first in the browser'); } return window['PUBNUB']; } else { // node.js return _dereq_('pubnub'); } } module.exports = PubNubTransport; },{"./../Transport":10,"./PubNubConnection":19,"pubnub":123}],21:[function(_dereq_,module,exports){ 'use strict'; var uuid = _dereq_('uuid-v4'); var Promise = _dereq_('promise'); var WebSocket = (typeof window !== 'undefined' && typeof window.WebSocket !== 'undefined') ? window.WebSocket : _dereq_('ws'); var util = _dereq_('../../util'); var Connection = _dereq_('../Connection'); /** * A websocket connection. * @param {WebSocketTransport} transport * @param {string | number | null} url The url of the agent. The url must match * the url of the WebSocket server. * If url is null, a UUID id is generated as url. * @param {function} receive * @constructor */ function WebSocketConnection(transport, url, receive) { this.transport = transport; this.url = url ? util.normalizeURL(url) : uuid(); this.receive = receive; this.sockets = {}; this.closed = false; this.reconnectTimers = {}; // ready state this.ready = Promise.resolve(this); } /** * Send a message to an agent. * @param {string} to The WebSocket url of the receiver * @param {*} message * @return {Promise} Returns a promise which resolves when the message is sent, * and rejects when sending the message failed */ WebSocketConnection.prototype.send = function (to, message) { //console.log('send', this.url, to, message); // TODO: cleanup // deliver locally when possible if (this.transport.localShortcut) { var agent = this.transport.agents[to]; if (agent) { try { agent.receive(this.url, message); return Promise.resolve(); } catch (err) { return Promise.reject(err); } } } // get or create a connection var conn = this.sockets[to]; if (conn) { try { if (conn.readyState == conn.CONNECTING) { // the connection is still opening return new Promise(function (resolve, reject) { conn.onopen.callback.push(function () { conn.send(JSON.stringify(message)); resolve(); }) }); } else if (conn.readyState == conn.OPEN) { conn.send(JSON.stringify(message)); return Promise.resolve(); } else { // remove the connection conn = null; } } catch (err) { return Promise.reject(err); } } if (!conn) { // try to open a connection var me = this; return new Promise(function (resolve, reject) { me._connect(to, function (conn) { conn.send(JSON.stringify(message)); resolve(); }, function (err) { reject(new Error('Failed to connect to agent "' + to + '"')); }); }) } }; /** * Open a websocket connection to an other agent. No messages are sent. * @param {string} to Url of the remote agent. * @returns {Promise.} * Returns a promise which resolves when the connection is * established and rejects in case of an error. */ WebSocketConnection.prototype.connect = function (to) { var me = this; return new Promise(function (resolve, reject) { me._connect(to, function () { resolve(me); }, reject); }); }; /** * Open a websocket connection * @param {String} to Url of the remote agent * @param {function} [callback] * @param {function} [errback] * @param {boolean} [doReconnect=false] * @returns {WebSocket} * @private */ WebSocketConnection.prototype._connect = function (to, callback, errback, doReconnect) { var me = this; var conn = new WebSocket(to + '?id=' + this.url); // register the new socket me.sockets[to] = conn; conn.onopen = function () { // Change doReconnect to true as soon as we have had an open connection doReconnect = true; conn.onopen.callbacks.forEach(function (cb) { cb(conn); }); conn.onopen.callbacks = []; }; conn.onopen.callbacks = callback ? [callback] : []; conn.onmessage = function (event) { me.receive(to, JSON.parse(event.data)); }; conn.onclose = function () { delete me.sockets[to]; if (doReconnect) { me._reconnect(to); } }; conn.onerror = function (err) { delete me.sockets[to]; if (errback) { errback(err); } }; return conn; }; /** * Auto reconnect a broken connection * @param {String} to Url of the remote agent * @private */ WebSocketConnection.prototype._reconnect = function (to) { var me = this; var doReconnect = true; if (me.closed == false && me.reconnectTimers[to] == null) { me.reconnectTimers[to] = setTimeout(function () { delete me.reconnectTimers[to]; me._connect(to, null, null, doReconnect); }, me.transport.reconnectDelay); } }; /** * Register a websocket connection * @param {String} from Url of the remote agent * @param {WebSocket} conn WebSocket connection * @returns {WebSocket} Returns the websocket itself * @private */ WebSocketConnection.prototype._onConnection = function (from, conn) { var me = this; conn.onmessage = function (event) { me.receive(from, JSON.parse(event.data)); }; conn.onclose = function () { // remove this connection from the sockets list delete me.sockets[from]; }; conn.onerror = function (err) { // TODO: what to do with errors? delete me.sockets[from]; }; if (this.sockets[from]) { // there is already a connection open with remote agent // TODO: what to do with overwriting existing sockets? this.sockets[from].close(); } // register new connection this.sockets[from] = conn; return conn; }; /** * Get a list with all open sockets * @return {String[]} Returns all open sockets */ WebSocketConnection.prototype.list = function () { return Object.keys(this.sockets); }; /** * Close the connection. All open sockets will be closed and the agent will * be unregistered from the WebSocketTransport. */ WebSocketConnection.prototype.close = function () { this.closed = true; // close all connections for (var id in this.sockets) { if (this.sockets.hasOwnProperty(id)) { this.sockets[id].close(); } } this.sockets = {}; delete this.transport.agents[this.url]; }; module.exports = WebSocketConnection; },{"../../util":23,"../Connection":9,"promise":114,"uuid-v4":125,"ws":126}],22:[function(_dereq_,module,exports){ 'use strict'; var urlModule = _dereq_('url'); var uuid = _dereq_('uuid-v4'); var Promise = _dereq_('promise'); var WebSocketServer = _dereq_('ws').Server; var util = _dereq_('../../util'); var Transport = _dereq_('../Transport'); var WebSocketConnection = _dereq_('./WebSocketConnection'); /** * Create a web socket transport. * @param {Object} config Config can contain the following properties: * - `id: string`. Optional * - `default: boolean`. Optional * - `url: string`. Optional. If provided, * A WebSocket server is started on given * url. * - `localShortcut: boolean`. Optional. If true * (default), messages to local agents are not * send via WebSocket but delivered immediately * - `reconnectDelay: number` Optional. Delay in * milliseconds for reconnecting a broken * connection. 10000 ms by default. Connections * are only automatically reconnected after * there has been an established connection. * @constructor */ function WebSocketTransport(config) { this.id = config && config.id || null; this.networkId = this.id || null; this['default'] = config && config['default'] || false; this.localShortcut = (config && config.localShortcut === false) ? false : true; this.reconnectDelay = config && config.reconnectDelay || 10000; this.url = config && config.url || null; this.server = null; if (this.url != null) { var urlParts = urlModule.parse(this.url); if (urlParts.protocol != 'ws:') throw new Error('Invalid protocol, "ws:" expected'); if (this.url.indexOf(':id') == -1) throw new Error('":id" placeholder missing in url'); this.address = urlParts.protocol + '//' + urlParts.host; // the url without path, for example 'ws://localhost:3000' this.ready = this._initServer(this.url); } else { this.address = null; this.ready = Promise.resolve(this); } this.agents = {}; // WebSocketConnections of all registered agents. The keys are the urls of the agents } WebSocketTransport.prototype = new Transport(); WebSocketTransport.prototype.type = 'ws'; /** * Build an url for given id. Example: * var url = getUrl('agent1'); // 'ws://localhost:3000/agents/agent1' * @param {String} id * @return {String} Returns the url, or returns null when no url placeholder * is defined. */ WebSocketTransport.prototype.getUrl = function (id) { return this.url ? this.url.replace(':id', id) : null; }; /** * Initialize a server on given url * @param {String} url For example 'http://localhost:3000' * @return {Promise} Returns a promise which resolves when the server is up * and running * @private */ WebSocketTransport.prototype._initServer = function (url) { var urlParts = urlModule.parse(url); var port = urlParts.port || 80; var me = this; return new Promise(function (resolve, reject) { me.server = new WebSocketServer({port: port}, function () { resolve(me); }); me.server.on('connection', me._onConnection.bind(me)); me.server.on('error', function (err) { reject(err) }); }) }; /** * Handle a new connection. The connection is added to the addressed agent. * @param {WebSocket} conn * @private */ WebSocketTransport.prototype._onConnection = function (conn) { var url = conn.upgradeReq.url; var urlParts = urlModule.parse(url, true); var toPath = urlParts.pathname; var to = util.normalizeURL(this.address + toPath); // read sender id from query parameters or generate a random uuid var queryParams = urlParts.query; var from = queryParams.id || uuid(); // TODO: make a config option to allow/disallow anonymous connections? //console.log('onConnection, to=', to, ', from=', from, ', agents:', Object.keys(this.agents)); // TODO: cleanup var agent = this.agents[to]; if (agent) { agent._onConnection(from, conn); } else { // reject the connection // conn.send('Error: Agent with id "' + to + '" not found'); // TODO: can we send back a message before closing? conn.close(); } }; /** * Connect an agent * @param {string} id The id or url of the agent. In case of an * url, this url should match the url of the * WebSocket server. * @param {Function} receive Invoked as receive(from, message) * @return {WebSocketConnection} Returns a promise which resolves when * connected. */ WebSocketTransport.prototype.connect = function(id, receive) { var isURL = (id.indexOf('://') !== -1); // FIXME: it's confusing right now what the final url will be based on the provided id... var url = isURL ? id : (this.getUrl(id) || id); if (url) url = util.normalizeURL(url); // register the agents receive function if (this.agents[url]) { throw new Error('Agent with id ' + this.id + ' already exists'); } var conn = new WebSocketConnection(this, url, receive); this.agents[conn.url] = conn; // use conn.url, url can be changed when it was null return conn; }; /** * Close the transport. Removes all agent connections. */ WebSocketTransport.prototype.close = function() { // close all connections for (var id in this.agents) { if (this.agents.hasOwnProperty(id)) { this.agents[id].close(); } } this.agents = {}; // close the server if (this.server) { this.server.close(); } }; module.exports = WebSocketTransport; },{"../../util":23,"../Transport":10,"./WebSocketConnection":21,"promise":114,"url":157,"uuid-v4":125,"ws":126}],23:[function(_dereq_,module,exports){ 'use strict'; /** * Test whether the provided value is a Promise. * A value is marked as a Promise when it is an object containing functions * `then` and `catch`. * @param {*} value * @return {boolean} Returns true when `value` is a Promise */ exports.isPromise = function (value) { return value && typeof value['then'] === 'function' && typeof value['catch'] === 'function' }; /** * Splits an url like "protocol://domain/path" * @param {string} url * @return {{protocol: string, domain: string, path: string} | null} * Returns an object with properties protocol, domain, and path * when there is a match. Returns null if no valid url. * */ exports.parseUrl = function (url) { // match an url like "protocol://domain/path" var match = /^([A-z]+):\/\/([^\/]+)(\/(.*)$|$)/.exec(url); if (match) { return { protocol: match[1], domain: match[2], path: match[4] } } return null; }; /** * Normalize a url. Removes trailing slash * @param {string} url * @return {string} Returns the normalized url */ exports.normalizeURL = function (url) { if (url[url.length - 1] == '/') { return url.substring(0, url.length - 1); } else { return url; } }; },{}],24:[function(_dereq_,module,exports){ 'use strict'; var Connection = _dereq_('./lib/connection'); module.exports = { Connection: Connection, createConnection: function (options, implOptions, readyCallback) { var c = new Connection(options, implOptions, readyCallback); c.connect(); return c; } }; },{"./lib/connection":28}],25:[function(_dereq_,module,exports){ // Copyright (c) 2008, Fair Oaks Labs, Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // * Neither the name of Fair Oaks Labs, Inc. nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Modified from original JSPack exports.jspack = function (bigEndian) { this.bigEndian = bigEndian; } exports.jspack.prototype._DeArray = function (a, p, l) { return [a.slice(p, p + l)]; }; exports.jspack.prototype._EnArray = function (a, p, l, v) { for (var i = 0; i < l; ++i) { a[p + i] = v[i] ? v[i] : 0; } }; exports.jspack.prototype._DeChar = function (a, p) { return String.fromCharCode(a[p]); }; exports.jspack.prototype._EnChar = function (a, p, v) { a[p] = v.charCodeAt(0); }; exports.jspack.prototype._DeInt = function (a, p) { var lsb = bigEndian ? format.len - 1 : 0; var nsb = bigEndian ? -1 : 1; var stp = lsb + nsb * format.len, rv; var ret = 0; var i = lsb; var f = 1; while (i != stp) { rv += a[p + i] * f; i += nsb; f *= 256; } if (format.signed) { if ((rv & Math.pow(2, format.len * 8 - 1)) != 0) { rv -= Math.pow(2, format.len * 8); } } return rv; }; exports.jspack.prototype._EnInt = function (a, p, v) { var lsb = bigEndian ? format.len - 1 : 0; var nsb = bigEndian ? -1 : 1; var stp = lsb + nsb * format.len; v = v < format.min ? format.min : ((v > format.max) ? format.max : v); var i = lsb; while (i != stp) { a[p + i] = v & 0xff; i += nsb; v >>= 8; } }; exports.jspack.prototype._DeString = function (a, p, l) { var rv = new Array(1); for (i = 0; i < l; i++) { rv[i] = String.fromCharCode(a[p + i]); } return rv.join(''); }; exports.jspack.prototype._EnString = function (a, p, l, v) { for (var t, i = 0; i < l; ++i) { t = v.charCodeAt(i); if (!t) t = 0; a[p + i] = t; } }; exports.jspack.prototype._De754 = function (a, p) { var s, e, m, i, d, bits, bit, len, bias, max; bit = format.bit; len = format.len * 8 - format.bit - 1; max = (1 << len) - 1; bias = max >> 1; i = bigEndian ? 0 : format.len - 1; d = bigEndian ? 1 : -1;; s = a[p + i]; i = i + d; bits = -7; e = s & ((1 << -bits) - 1); s >>= -bits; for (bits += len; bits > 0; bits -= 8) { e = e * 256 + a[p + i]; i += d; } m = e & ((1 << -bits) - 1); e >>= -bits; for (bits += bit; bits > 0; bits -= 8) { m = m * 256 + a[p + i]; i += d; } switch (e) { case 0: // Zero, or denormalized number e = 1 - bias; break; case max: // NaN, or +/-Infinity return m ? NaN : ((s ? -1 : 1) * Infinity); default: // Normalized number m = m + Math.pow(2, bit); e = e - bias; break; } return (s ? -1 : 1) * m * Math.pow(2, e - bit); }; exports.jspack.prototype._En754 = function (a, p, v) { var s, e, m, i, d, c, bit, len, bias, max; bit = format.bit; len = format.len * 8 - format.bit - 1; max = (1 << len) - 1; bias = max >> 1; s = v < 0 ? 1 : 0; v = Math.abs(v); if (isNaN(v) || (v == Infinity)) { m = isNaN(v) ? 1 : 0; e = max; } else { e = Math.floor(Math.log(v) / Math.LN2); // Calculate log2 of the value c = Math.pow(2, -e); if (v * c < 1) { e--; c = c * 2; } // Round by adding 1/2 the significand's LSD if (e + bias >= 1) { v += format.rt / c; // Normalized: bit significand digits } else { v += format.rt * Math.pow(2, 1 - bias); // Denormalized: <= bit significand digits } if (v * c >= 2) { e++; c = c / 2; // Rounding can increment the exponent } if (e + bias >= max) { // overflow m = 0; e = max; } else if (e + bias >= 1) { // normalized m = (v * c - 1) * Math.pow(2, bit); // do not reorder this expression e = e + bias; } else { // Denormalized - also catches the '0' case, somewhat by chance m = v * Math.pow(2, bias - 1) * Math.pow(2, bit); e = 0; } } i = bigEndian ? format.len - 1 : 0; d = bigEndian ? -1 : 1;; while (bit >= 8) { a[p + i] = m & 0xff; i += d; m /= 256; bit -= 8; } e = (e << bit) | m; for (len += bit; len > 0; len -= 8) { a[p + i] = e & 0xff; i += d; e /= 256; } a[p + i - d] |= s * 128; }; // Unpack a series of n formatements of size s from array a at offset p with fxn exports.jspack.prototype._UnpackSeries = function (n, s, a, p) { var fxn = format.de; var ret = []; for (var i = 0; i < n; i++) { ret.push(fxn(a, p + i * s)); } return ret; }; // Pack a series of n formatements of size s from array v at offset i to array a at offset p with fxn exports.jspack.prototype._PackSeries = function (n, s, a, p, v, i) { var fxn = format.en; for (o = 0; o < n; o++) { fxn(a, p + o * s, v[i + o]); } }; // Unpack the octet array a, beginning at offset p, according to the fmt string exports.jspack.prototype.Unpack = function (fmt, a, p) { bigEndian = fmt.charAt(0) != '<'; if (p == undefined || p == null) p = 0; var re = new RegExp(this._sPattern, 'g'); var ret = []; for (var m; m = re.exec(fmt); /* */ ) { var n; if (m[1] == undefined || m[1] == '') n = 1; else n = parseInt(m[1]); var s = this._lenLut[m[2]]; if ((p + n * s) > a.length) return undefined; switch (m[2]) { case 'A': case 's': rv.push(this._formatLut[m[2]].de(a, p, n)); break; case 'c': case 'b': case 'B': case 'h': case 'H': case 'i': case 'I': case 'l': case 'L': case 'f': case 'd': format = this._formatLut[m[2]]; ret.push(this._UnpackSeries(n, s, a, p)); break; } p += n * s; } return Array.prototype.concat.apply([], ret); }; // Pack the supplied values into the octet array a, beginning at offset p, according to the fmt string exports.jspack.prototype.PackTo = function (fmt, a, p, values) { bigEndian = (fmt.charAt(0) != '<'); var re = new RegExp(this._sPattern, 'g'); for (var m, i = 0; m = re.exec(fmt); /* */ ) { var n; if (m[1] == undefined || m[1] == '') n = 1; else n = parseInt(m[1]); var s = this._lenLut[m[2]]; if ((p + n * s) > a.length) return false; switch (m[2]) { case 'A': case 's': if ((i + 1) > values.length) return false; this._formatLut[m[2]].en(a, p, n, values[i]); i += 1; break; case 'c': case 'b': case 'B': case 'h': case 'H': case 'i': case 'I': case 'l': case 'L': case 'f': case 'd': format = this._formatLut[m[2]]; if (i + n > values.length) return false; this._PackSeries(n, s, a, p, values, i); i += n; break; case 'x': for (var j = 0; j < n; j++) { a[p + j] = 0; } break; } p += n * s; } return a; }; // Pack the supplied values into a new octet array, according to the fmt string exports.jspack.prototype.Pack = function (fmt, values) { return this.PackTo(fmt, new Array(this.CalcLength(fmt)), 0, values); }; // Determine the number of bytes represented by the format string exports.jspack.prototype.CalcLength = function (fmt) { var re = new RegExp(this._sPattern, 'g'); var sz = 0; while (match = re.exec(fmt)) { var n; if (match[1] == undefined || match[1] == '') n = 1; else n = parseInt(match[1]); sz += n * this._lenLut[match[2]]; } return sz; }; // Regular expression for counting digits exports.jspack.prototype._sPattern = '(\\d+)?([AxcbBhHsfdiIlL])'; // Byte widths for associated formats exports.jspack.prototype._lenLut = { 'A': 1, 'x': 1, 'c': 1, 'b': 1, 'B': 1, 'h': 2, 'H': 2, 's': 1, 'f': 4, 'd': 8, 'i': 4, 'I': 4, 'l': 4, 'L': 4 }; exports.jspack.prototype._formatLut = { 'A': { en: exports.jspack.prototype._EnArray, de: exports.jspack.prototype._DeArray }, 's': { en: exports.jspack.prototype._EnString, de: exports.jspack.prototype._DeString }, 'c': { en: exports.jspack.prototype._EnChar, de: exports.jspack.prototype._DeChar }, 'b': { en: exports.jspack.prototype._EnInt, de: exports.jspack.prototype._DeInt, len: 1, signed: true, min: -Math.pow(2, 7), max: Math.pow(2, 7) - 1 }, 'B': { en: exports.jspack.prototype._EnInt, de: exports.jspack.prototype._DeInt, len: 1, signed: false, min: 0, max: Math.pow(2, 8) - 1 }, 'h': { en: exports.jspack.prototype._EnInt, de: exports.jspack.prototype._DeInt, len: 2, signed: true, min: -Math.pow(2, 15), max: Math.pow(2, 15) - 1 }, 'H': { en: exports.jspack.prototype._EnInt, de: exports.jspack.prototype._DeInt, len: 2, signed: false, min: 0, max: Math.pow(2, 16) - 1 }, 'i': { en: exports.jspack.prototype._EnInt, de: exports.jspack.prototype._DeInt, len: 4, signed: true, min: -Math.pow(2, 31), max: Math.pow(2, 31) - 1 }, 'I': { en: exports.jspack.prototype._EnInt, de: exports.jspack.prototype._DeInt, len: 4, signed: false, min: 0, max: Math.pow(2, 32) - 1 }, 'l': { en: exports.jspack.prototype._EnInt, de: exports.jspack.prototype._DeInt, len: 4, signed: true, min: -Math.pow(2, 31), max: Math.pow(2, 31) - 1 }, 'L': { en: exports.jspack.prototype._EnInt, de: exports.jspack.prototype._DeInt, len: 4, signed: false, min: 0, max: Math.pow(2, 32) - 1 }, 'f': { en: exports.jspack.prototype._En754, de: exports.jspack.prototype._De754, len: 4, bit: 23, rt: Math.pow(2, -24) - Math.pow(2, -77) }, 'd': { en: exports.jspack.prototype._En754, de: exports.jspack.prototype._De754, len: 8, bit: 52, rt: 0 } }; },{}],26:[function(_dereq_,module,exports){ exports.constants = [ [1, "frameMethod"], [2, "frameHeader"], [3, "frameBody"], [8, "frameHeartbeat"], [200, "replySuccess"], [206, "frameEnd"], [311, "contentTooLarge"], [313, "noConsumers"], [320, "connectionForced"], [402, "invalidPath"], [403, "accessRefused"], [404, "notFound"], [405, "resourceLocked"], [406, "preconditionFailed"], [501, "frameError"], [502, "syntaxError"], [503, "commandInvalid"], [504, "channelError"], [505, "unexpectedFrame"], [506, "resourceError"], [530, "notAllowed"], [540, "notImplemented"], [541, "internalError"], [4096, "frameMinSize"] ]; exports.classes = [{ "name": "connection", "index": 10, "fields": [], "methods": [{ "name": "start", "index": 10, "fields": [{ "name": "versionMajor", "domain": "octet" }, { "name": "versionMinor", "domain": "octet" }, { "name": "serverProperties", "domain": "table" }, { "name": "mechanisms", "domain": "longstr" }, { "name": "locales", "domain": "longstr" }] }, { "name": "startOk", "index": 11, "fields": [{ "name": "clientProperties", "domain": "table" }, { "name": "mechanism", "domain": "shortstr" }, { "name": "response", "domain": "longstr" }, { "name": "locale", "domain": "shortstr" }] }, { "name": "secure", "index": 20, "fields": [{ "name": "challenge", "domain": "longstr" }] }, { "name": "secureOk", "index": 21, "fields": [{ "name": "response", "domain": "longstr" }] }, { "name": "tune", "index": 30, "fields": [{ "name": "channelMax", "domain": "short" }, { "name": "frameMax", "domain": "long" }, { "name": "heartbeat", "domain": "short" }] }, { "name": "tuneOk", "index": 31, "fields": [{ "name": "channelMax", "domain": "short" }, { "name": "frameMax", "domain": "long" }, { "name": "heartbeat", "domain": "short" }] }, { "name": "open", "index": 40, "fields": [{ "name": "virtualHost", "domain": "shortstr" }, { "name": "reserved1", "domain": "shortstr" }, { "name": "reserved2", "domain": "bit" }] }, { "name": "openOk", "index": 41, "fields": [{ "name": "reserved1", "domain": "shortstr" }] }, { "name": "close", "index": 50, "fields": [{ "name": "replyCode", "domain": "short" }, { "name": "replyText", "domain": "shortstr" }, { "name": "classId", "domain": "short" }, { "name": "methodId", "domain": "short" }] }, { "name": "closeOk", "index": 51, "fields": [] }] }, { "name": "channel", "index": 20, "fields": [], "methods": [{ "name": "open", "index": 10, "fields": [{ "name": "reserved1", "domain": "shortstr" }] }, { "name": "openOk", "index": 11, "fields": [{ "name": "reserved1", "domain": "longstr" }] }, { "name": "flow", "index": 20, "fields": [{ "name": "active", "domain": "bit" }] }, { "name": "flowOk", "index": 21, "fields": [{ "name": "active", "domain": "bit" }] }, { "name": "close", "index": 40, "fields": [{ "name": "replyCode", "domain": "short" }, { "name": "replyText", "domain": "shortstr" }, { "name": "classId", "domain": "short" }, { "name": "methodId", "domain": "short" }] }, { "name": "closeOk", "index": 41, "fields": [] }] }, { "name": "exchange", "index": 40, "fields": [], "methods": [{ "name": "declare", "index": 10, "fields": [{ "name": "reserved1", "domain": "short" }, { "name": "exchange", "domain": "shortstr" }, { "name": "type", "domain": "shortstr" }, { "name": "passive", "domain": "bit" }, { "name": "durable", "domain": "bit" }, { "name": "autoDelete", "domain": "bit" }, { "name": "reserved2", "domain": "bit" }, { "name": "reserved3", "domain": "bit" }, { "name": "noWait", "domain": "bit" }, { "name": "arguments", "domain": "table" }] }, { "name": "declareOk", "index": 11, "fields": [] }, { "name": "delete", "index": 20, "fields": [{ "name": "reserved1", "domain": "short" }, { "name": "exchange", "domain": "shortstr" }, { "name": "ifUnused", "domain": "bit" }, { "name": "noWait", "domain": "bit" }] }, { "name": "deleteOk", "index": 21, "fields": [] }, { "name": "bind", "index": 30, "fields": [{ "name": "reserved1", "domain": "short" }, { "name": "destination", "domain": "shortstr" }, { "name": "source", "domain": "shortstr" }, { "name": "routingKey", "domain": "shortstr" }, { "name": "noWait", "domain": "bit" }, { "name": "arguments", "domain": "table" }] }, { "name": "bindOk", "index": 31, "fields": [] }, { "name": "unbind", "index": 40, "fields": [{ "name": "reserved1", "domain": "short" }, { "name": "destination", "domain": "shortstr" }, { "name": "source", "domain": "shortstr" }, { "name": "routingKey", "domain": "shortstr" }, { "name": "noWait", "domain": "bit" }, { "name": "arguments", "domain": "table" }] }, { "name": "unbindOk", "index": 51, "fields": [] }] }, { "name": "queue", "index": 50, "fields": [], "methods": [{ "name": "declare", "index": 10, "fields": [{ "name": "reserved1", "domain": "short" }, { "name": "queue", "domain": "shortstr" }, { "name": "passive", "domain": "bit" }, { "name": "durable", "domain": "bit" }, { "name": "exclusive", "domain": "bit" }, { "name": "autoDelete", "domain": "bit" }, { "name": "noWait", "domain": "bit" }, { "name": "arguments", "domain": "table" }] }, { "name": "declareOk", "index": 11, "fields": [{ "name": "queue", "domain": "shortstr" }, { "name": "messageCount", "domain": "long" }, { "name": "consumerCount", "domain": "long" }] }, { "name": "bind", "index": 20, "fields": [{ "name": "reserved1", "domain": "short" }, { "name": "queue", "domain": "shortstr" }, { "name": "exchange", "domain": "shortstr" }, { "name": "routingKey", "domain": "shortstr" }, { "name": "noWait", "domain": "bit" }, { "name": "arguments", "domain": "table" }] }, { "name": "bindOk", "index": 21, "fields": [] }, { "name": "unbind", "index": 50, "fields": [{ "name": "reserved1", "domain": "short" }, { "name": "queue", "domain": "shortstr" }, { "name": "exchange", "domain": "shortstr" }, { "name": "routingKey", "domain": "shortstr" }, { "name": "arguments", "domain": "table" }] }, { "name": "unbindOk", "index": 51, "fields": [] }, { "name": "purge", "index": 30, "fields": [{ "name": "reserved1", "domain": "short" }, { "name": "queue", "domain": "shortstr" }, { "name": "noWait", "domain": "bit" }] }, { "name": "purgeOk", "index": 31, "fields": [{ "name": "messageCount", "domain": "long" }] }, { "name": "delete", "index": 40, "fields": [{ "name": "reserved1", "domain": "short" }, { "name": "queue", "domain": "shortstr" }, { "name": "ifUnused", "domain": "bit" }, { "name": "ifEmpty", "domain": "bit" }, { "name": "noWait", "domain": "bit" }] }, { "name": "deleteOk", "index": 41, "fields": [{ "name": "messageCount", "domain": "long" }] }] }, { "name": "basic", "index": 60, "fields": [{ "name": "contentType", "domain": "shortstr" }, { "name": "contentEncoding", "domain": "shortstr" }, { "name": "headers", "domain": "table" }, { "name": "deliveryMode", "domain": "octet" }, { "name": "priority", "domain": "octet" }, { "name": "correlationId", "domain": "shortstr" }, { "name": "replyTo", "domain": "shortstr" }, { "name": "expiration", "domain": "shortstr" }, { "name": "messageId", "domain": "shortstr" }, { "name": "timestamp", "domain": "timestamp" }, { "name": "type", "domain": "shortstr" }, { "name": "userId", "domain": "shortstr" }, { "name": "appId", "domain": "shortstr" }, { "name": "reserved", "domain": "shortstr" }], "methods": [{ "name": "qos", "index": 10, "fields": [{ "name": "prefetchSize", "domain": "long" }, { "name": "prefetchCount", "domain": "short" }, { "name": "global", "domain": "bit" }] }, { "name": "qosOk", "index": 11, "fields": [] }, { "name": "consume", "index": 20, "fields": [{ "name": "reserved1", "domain": "short" }, { "name": "queue", "domain": "shortstr" }, { "name": "consumerTag", "domain": "shortstr" }, { "name": "noLocal", "domain": "bit" }, { "name": "noAck", "domain": "bit" }, { "name": "exclusive", "domain": "bit" }, { "name": "noWait", "domain": "bit" }, { "name": "arguments", "domain": "table" }] }, { "name": "consumeOk", "index": 21, "fields": [{ "name": "consumerTag", "domain": "shortstr" }] }, { "name": "cancel", "index": 30, "fields": [{ "name": "consumerTag", "domain": "shortstr" }, { "name": "noWait", "domain": "bit" }] }, { "name": "cancelOk", "index": 31, "fields": [{ "name": "consumerTag", "domain": "shortstr" }] }, { "name": "publish", "index": 40, "fields": [{ "name": "reserved1", "domain": "short" }, { "name": "exchange", "domain": "shortstr" }, { "name": "routingKey", "domain": "shortstr" }, { "name": "mandatory", "domain": "bit" }, { "name": "immediate", "domain": "bit" }] }, { "name": "return", "index": 50, "fields": [{ "name": "replyCode", "domain": "short" }, { "name": "replyText", "domain": "shortstr" }, { "name": "exchange", "domain": "shortstr" }, { "name": "routingKey", "domain": "shortstr" }] }, { "name": "deliver", "index": 60, "fields": [{ "name": "consumerTag", "domain": "shortstr" }, { "name": "deliveryTag", "domain": "longlong" }, { "name": "redelivered", "domain": "bit" }, { "name": "exchange", "domain": "shortstr" }, { "name": "routingKey", "domain": "shortstr" }] }, { "name": "get", "index": 70, "fields": [{ "name": "reserved1", "domain": "short" }, { "name": "queue", "domain": "shortstr" }, { "name": "noAck", "domain": "bit" }] }, { "name": "getOk", "index": 71, "fields": [{ "name": "deliveryTag", "domain": "longlong" }, { "name": "redelivered", "domain": "bit" }, { "name": "exchange", "domain": "shortstr" }, { "name": "routingKey", "domain": "shortstr" }, { "name": "messageCount", "domain": "long" }] }, { "name": "getEmpty", "index": 72, "fields": [{ "name": "reserved1", "domain": "shortstr" }] }, { "name": "ack", "index": 80, "fields": [{ "name": "deliveryTag", "domain": "longlong" }, { "name": "multiple", "domain": "bit" }] }, { "name": "reject", "index": 90, "fields": [{ "name": "deliveryTag", "domain": "longlong" }, { "name": "requeue", "domain": "bit" }] }, { "name": "recoverAsync", "index": 100, "fields": [{ "name": "requeue", "domain": "bit" }] }, { "name": "recover", "index": 110, "fields": [{ "name": "requeue", "domain": "bit" }] }, { "name": "recoverOk", "index": 111, "fields": [] }] }, { "name": "tx", "index": 90, "fields": [], "methods": [{ "name": "select", "index": 10, "fields": [] }, { "name": "selectOk", "index": 11, "fields": [] }, { "name": "commit", "index": 20, "fields": [] }, { "name": "commitOk", "index": 21, "fields": [] }, { "name": "rollback", "index": 30, "fields": [] }, { "name": "rollbackOk", "index": 31, "fields": [] }] }, { "name": "confirm", "index": 85, "fields": [], "methods": [{ "name": "select", "index": 10, "fields": [{ "name": "noWait", "domain": "bit" }] }, { "name": "selectOk", "index": 11, "fields": [] }] }]; },{}],27:[function(_dereq_,module,exports){ 'use strict'; var events = _dereq_('events'); var util = _dereq_('util'); var fs = _dereq_('fs'); var Promise = _dereq_('./promise').Promise; var definitions = _dereq_('./definitions'); var methods = definitions.methods; // This class is not exposed to the user. Queue and Exchange are subclasses // of Channel. This just provides a task queue. var Channel = module.exports = function Channel (connection, channel) { events.EventEmitter.call(this); // Unlimited listeners. Helps when e.g. publishing high-volume messages, // 10 is far too low. this.setMaxListeners(0); this.channel = channel; this.connection = connection; this._tasks = []; this.reconnect(); }; util.inherits(Channel, events.EventEmitter); Channel.prototype.closeOK = function() { this.connection._sendMethod(this.channel, methods.channelCloseOk, {reserved1: ""}); }; Channel.prototype.reconnect = function () { this.connection._sendMethod(this.channel, methods.channelOpen, {reserved1: ""}); }; Channel.prototype._taskPush = function (reply, cb) { var promise = new Promise(); this._tasks.push({ promise: promise, reply: reply, sent: false, cb: cb }); this._tasksFlush(); return promise; }; Channel.prototype._tasksFlush = function () { if (this.state != 'open') return; for (var i = 0; i < this._tasks.length; i++) { var task = this._tasks[i]; if (task.sent) continue; task.cb(); task.sent = true; if (!task.reply) { // if we don't expect a reply, just delete it now this._tasks.splice(i, 1); i = i-1; } } }; Channel.prototype._handleTaskReply = function (channel, method, args) { var task, i; for (i = 0; i < this._tasks.length; i++) { if (this._tasks[i].reply == method) { task = this._tasks[i]; this._tasks.splice(i, 1); task.promise.emitSuccess(args); this._tasksFlush(); return true; } } return false; }; Channel.prototype._onChannelMethod = function(channel, method, args) { switch (method) { case methods.channelCloseOk: delete this.connection.channels[this.channel]; this.state = 'closed'; // TODO should this be falling through? default: this._onMethod(channel, method, args); } }; Channel.prototype.close = function(reason) { this.state = 'closing'; this.connection._sendMethod(this.channel, methods.channelClose, {'replyText': reason ? reason : 'Goodbye from node', 'replyCode': 200, 'classId': 0, 'methodId': 0}); }; },{"./definitions":31,"./promise":35,"events":137,"fs":127,"util":159}],28:[function(_dereq_,module,exports){ (function (process,Buffer){ 'use strict'; var net = _dereq_('net'); var tls = _dereq_('tls'); var fs = _dereq_('fs'); var URL = _dereq_('url'); var _ = _dereq_('lodash'); var debug = _dereq_('./debug'); var EventEmitter = _dereq_('events').EventEmitter; var util = _dereq_('util'); var serializer = _dereq_('./serializer'); var definitions = _dereq_('./definitions'); var methods = definitions.methods; var methodTable = definitions.methodTable; var classes = definitions.classes; var Exchange = _dereq_('./exchange'); var Queue = _dereq_('./queue'); var AMQPParser = _dereq_('./parser'); var nodeAMQPVersion = _dereq_('../package').version; var maxFrameBuffer = 131072; // 128k, same as rabbitmq (which was // copying qpid) var defaultPorts = { 'amqp': 5672, 'amqps': 5671 }; var defaultOptions = { host: 'localhost', port: defaultPorts['amqp'], login: 'guest', password: 'guest', authMechanism: 'AMQPLAIN', vhost: '/', connectionTimeout: 10000, ssl: { enabled: false } }; var defaultSslOptions = { port: defaultPorts['amqps'], ssl: { rejectUnauthorized: true } }; var defaultImplOptions = { defaultExchangeName: '', reconnect: true, reconnectBackoffStrategy: 'linear', reconnectExponentialLimit: 120000, reconnectBackoffTime: 1000 }; var defaultClientProperties = { version: nodeAMQPVersion, platform: 'node-' + process.version, product: 'node-amqp' }; var Connection = module.exports = function Connection (connectionArgs, options, readyCallback) { EventEmitter.call(this); this.setOptions(connectionArgs); this.setImplOptions(options); if (typeof readyCallback === 'function') { this._readyCallback = readyCallback; } this.connectionAttemptScheduled = false; this._defaultExchange = null; this.channelCounter = 0; this._sendBuffer = new Buffer(maxFrameBuffer); }; util.inherits(Connection, EventEmitter); Connection.prototype.setOptions = function (options) { var urlo = (options && options.url) ? this._parseURLOptions(options.url) : {}; var sslo = (options && options.ssl && options.ssl.enabled) ? defaultSslOptions : {}; this.options = _.extend({}, defaultOptions, sslo, urlo, options || {}); this.options.clientProperties = _.extend({}, defaultClientProperties, (options && options.clientProperties) || {}); }; Connection.prototype.setImplOptions = function (options) { this.implOptions = _.extend({}, defaultImplOptions, options || {}); }; Connection.prototype.connect = function () { // If this is our first connection, add listeners. if (!this.socket) this.addAllListeners(); this._createSocket(); this._startHandshake(); }; Connection.prototype.reconnect = function () { // Suspend activity on channels for (var channel in this.channels) { this.channels[channel].state = 'closed'; } debug && debug("Connection lost, reconnecting..."); // Terminate socket activity if (this.socket) this.socket.end(); this.connect(); }; Connection.prototype.disconnect = function () { debug && debug("Sending disconnect request to server"); this._sendMethod(0, methods.connectionClose, { 'replyText': 'client disconnect', 'replyCode': 200, 'classId': 0, 'methodId': 0 }); }; Connection.prototype.addAllListeners = function() { var self = this; var connectEvent = this.options.ssl.enabled ? 'secureConnect' : 'connect'; self.addListener(connectEvent, function() { // In the case where this is a reconnection, do not trample on the existing // channels. // For your reference, channel 0 is the control channel. self.channels = self.channels || {0:self}; self.queues = self.queues || {}; self.exchanges = self.exchanges || {}; self.parser = new AMQPParser('0-9-1', 'client'); self.parser.onMethod = function (channel, method, args) { self._onMethod(channel, method, args); }; self.parser.onContent = function (channel, data) { debug && debug(channel + " > content " + data.length); if (self.channels[channel] && self.channels[channel]._onContent) { self.channels[channel]._onContent(channel, data); } else { debug && debug("unhandled content: " + data); } }; self.parser.onContentHeader = function (channel, classInfo, weight, properties, size) { debug && debug(channel + " > content header " + JSON.stringify([classInfo.name, weight, properties, size])); if (self.channels[channel] && self.channels[channel]._onContentHeader) { self.channels[channel]._onContentHeader(channel, classInfo, weight, properties, size); } else { debug && debug("unhandled content header"); } }; self.parser.onHeartBeat = function () { self.emit("heartbeat"); debug && debug("heartbeat"); }; self.parser.onError = function (e) { self.emit("error", e); self.emit("close"); }; // Remove readyEmitted flag so we can detect an auth error. self.readyEmitted = false; }); self.addListener('data', function (data) { if(self.parser != null){ try { self.parser.execute(data); } catch (exception) { self.emit('error', exception); return; } } self._inboundHeartbeatTimerReset(); }); var backoffTime = null; self.addListener('error', function backoff(e) { if (self._inboundHeartbeatTimer !== null) { clearTimeout(self._inboundHeartbeatTimer); self._inboundHeartbeatTimer = null; } if (self._outboundHeartbeatTimer !== null) { clearTimeout(self._outboundHeartbeatTimer); self._outboundHeartbeatTimer = null; } if (!self.connectionAttemptScheduled) { // Set to true, as we are presently in the process of scheduling one. self.connectionAttemptScheduled = true; // Kill the socket, if it hasn't been killed already. self.socket.end(); // Reset parser state self.parser = null; // In order for our reconnection to be seamless, we have to notify the // channels that they are no longer connected so that nobody attempts // to send messages which would be doomed to fail. for (var channel in self.channels) { if (channel !== 0) { self.channels[channel].state = 'closed'; } } // Queues are channels (so we have already marked them as closed), but // queues have special needs, since the subscriptions will no longer // be known to the server when we reconnect. Mark the subscriptions as // closed so that we can resubscribe them once we are reconnected. for (var queue in self.queues) { for (var index in self.queues[queue].consumerTagOptions) { self.queues[queue].consumerTagOptions[index]['state'] = 'closed'; } } // Begin reconnection attempts if (self.implOptions.reconnect) { // Don't thrash, use a backoff strategy. if (backoffTime === null) { // This is the first time we've failed since a successful connection, // so use the configured backoff time without any modification. backoffTime = self.implOptions.reconnectBackoffTime; } else if (self.implOptions.reconnectBackoffStrategy === 'exponential') { // If you've configured exponential backoff, we'll double the // backoff time each subsequent attempt until success. backoffTime *= 2; // limit the maxium timeout, to avoid potentially unlimited stalls if(backoffTime > self.implOptions.reconnectExponentialLimit){ backoffTime = self.implOptions.reconnectExponentialLimit; } } else if (self.implOptions.reconnectBackoffStrategy === 'linear') { // Linear strategy is the default. In this case, we will retry at a // constant interval, so there's no need to change the backoff time // between attempts. } else { // TODO should we warn people if they picked a nonexistent strategy? } setTimeout(function () { // Set to false, so that if we fail in the reconnect attempt, we can // schedule another one. self.connectionAttemptScheduled = false; self.reconnect(); }, backoffTime); } else { self.removeListener('error', backoff); } } }); self.addListener('ready', function () { // Reset the backoff time since we have successfully connected. backoffTime = null; if (self.implOptions.reconnect) { // Reconnect any channels which were open. _.each(self.channels, function(channel, index) { // FIXME why is the index "0" instead of 0? if (index !== "0") channel.reconnect(); }); } // Set 'ready' flag for auth failure detection. this.readyEmitted = true; // Restart the heartbeat to the server self._outboundHeartbeatTimerReset(); }); // Apparently, it is not possible to determine if an authentication error // has occurred, but when the connection closes then we can HINT that a // possible authentication error has occured. Although this may be a bug // in the spec, handling it as a possible error is considerably better than // failing silently. self.addListener('end', function (){ if (!this.readyEmitted){ this.emit('error', new Error( 'Connection ended: possibly due to an authentication failure.' )); } }); }; Connection.prototype.heartbeat = function () { if(this.socket.writable) this.write(new Buffer([8,0,0,0,0,0,0,206])); }; // connection.exchange('my-exchange', { type: 'topic' }); // Options // - type 'fanout', 'direct', or 'topic' (default) // - passive (boolean) // - durable (boolean) // - autoDelete (boolean, default true) Connection.prototype.exchange = function (name, options, openCallback) { if (name === undefined) name = this.implOptions.defaultExchangeName; if (!options) options = {}; if (name !== '' && options.type === undefined) options.type = 'topic'; try{ var channel = this.generateChannelId(); }catch(exception){ this.emit("error", exception); return; } var exchange = new Exchange(this, channel, name, options, openCallback); this.channels[channel] = exchange; this.exchanges[name] = exchange; return exchange; }; // remove an exchange when it's closed (called from Exchange) Connection.prototype.exchangeClosed = function (name) { if (this.exchanges[name]) delete this.exchanges[name]; }; // Options // - passive (boolean) // - durable (boolean) // - exclusive (boolean) // - autoDelete (boolean, default true) Connection.prototype.queue = function (name /* options, openCallback */) { var options, callback; if (typeof arguments[1] == 'object') { options = arguments[1]; callback = arguments[2]; } else { callback = arguments[1]; } try{ var channel = this.generateChannelId(); }catch(exception){ this.emit("error", exception); return; } var q = new Queue(this, channel, name, options, callback); this.channels[channel] = q; return q; }; // remove a queue when it's closed (called from Queue) Connection.prototype.queueClosed = function (name) { if (this.queues[name]) delete this.queues[name]; }; // Publishes a message to the default exchange. Connection.prototype.publish = function (routingKey, body, options, callback) { if (!this._defaultExchange) this._defaultExchange = this.exchange(); return this._defaultExchange.publish(routingKey, body, options, callback); }; Connection.prototype._bodyToBuffer = function (body) { // Handles 3 cases // - body is utf8 string // - body is instance of Buffer // - body is an object and its JSON representation is sent // Does not handle the case for streaming bodies. // Returns buffer. if (typeof(body) == 'string') { return [null, new Buffer(body, 'utf8')]; } else if (body instanceof Buffer) { return [null, body]; } else { var jsonBody = JSON.stringify(body); debug && debug('sending json: ' + jsonBody); var props = {contentType: 'application/json'}; return [props, new Buffer(jsonBody, 'utf8')]; } }; Connection.prototype._inboundHeartbeatTimerReset = function () { if (this._inboundHeartbeatTimer !== null) { clearTimeout(this._inboundHeartbeatTimer); this._inboundHeartbeatTimer = null; } if (this.options.heartbeat) { var self = this; var gracePeriod = 2 * this.options.heartbeat; this._inboundHeartbeatTimer = setTimeout(function () { if(self.socket.readable) self.emit('error', new Error('no heartbeat or data in last ' + gracePeriod + ' seconds')); }, gracePeriod * 1000); } }; Connection.prototype._outboundHeartbeatTimerReset = function () { if (this._outboundHeartbeatTimer !== null) { clearTimeout(this._outboundHeartbeatTimer); this._outboundHeartbeatTimer = null; } if (this.socket.writable && this.options.heartbeat) { var self = this; this._outboundHeartbeatTimer = setTimeout(function () { self.heartbeat(); self._outboundHeartbeatTimerReset(); }, 1000 * this.options.heartbeat); } }; Connection.prototype._onMethod = function (channel, method, args) { debug && debug(channel + " > " + method.name + " " + JSON.stringify(args)); // Channel 0 is the control channel. If not zero then delegate to // one of the channel objects. if (channel > 0) { if (!this.channels[channel]) { debug && debug("Received message on untracked channel."); return; } if (!this.channels[channel]._onChannelMethod) { throw new Error('Channel ' + channel + ' has no _onChannelMethod method.'); } this.channels[channel]._onChannelMethod(channel, method, args); return; } // channel 0 switch (method) { // 2. The server responds, after the version string, with the // 'connectionStart' method (contains various useless information) case methods.connectionStart: // We check that they're serving us AMQP 0-9 if (args.versionMajor !== 0 && args.versionMinor != 9) { this.socket.end(); this.emit('error', new Error("Bad server version")); return; } this.serverProperties = args.serverProperties; // 3. Then we reply with StartOk, containing our useless information. this._sendMethod(0, methods.connectionStartOk, { clientProperties: this.options.clientProperties, mechanism: this.options.authMechanism, response: { LOGIN: this.options.login, PASSWORD: this.options.password }, locale: 'en_US' }); break; // 4. The server responds with a connectionTune request case methods.connectionTune: if (args.frameMax) { debug && debug("tweaking maxFrameBuffer to " + args.frameMax); maxFrameBuffer = args.frameMax; } // 5. We respond with connectionTuneOk this._sendMethod(0, methods.connectionTuneOk, { channelMax: 0, frameMax: maxFrameBuffer, heartbeat: this.options.heartbeat || 0 }); // 6. Then we have to send a connectionOpen request this._sendMethod(0, methods.connectionOpen, { virtualHost: this.options.vhost // , capabilities: '' // , insist: true , reserved1: '', reserved2: true }); break; case methods.connectionOpenOk: // 7. Finally they respond with connectionOpenOk // Whew! That's why they call it the Advanced MQP. if (this._readyCallback) { this._readyCallback(this); this._readyCallback = null; } this.emit('ready'); break; case methods.connectionClose: var e = new Error(args.replyText); e.code = args.replyCode; if (!this.listeners('close').length) { console.log('Unhandled connection error: ' + args.replyText); } this.socket.destroy(e); break; case methods.connectionCloseOk: debug && debug("Received close-ok from server, closing socket"); this.socket.end(); break; default: throw new Error("Uncaught method '" + method.name + "' with args " + JSON.stringify(args)); } }; // Generate connection options from URI string formatted with amqp scheme. Connection.prototype._parseURLOptions = function(connectionString) { var opts = {}; opts.ssl = {}; var url = URL.parse(connectionString); var scheme = url.protocol.substring(0, url.protocol.lastIndexOf(':')); if (scheme != 'amqp' && scheme != 'amqps') { throw new Error('Connection URI must use amqp or amqps scheme. ' + 'For example, "amqp://bus.megacorp.internal:5766".'); } opts.ssl.enabled = ('amqps' === scheme); opts.host = url.hostname; opts.port = url.port || defaultPorts[scheme]; if (url.auth) { var auth = url.auth.split(':'); auth[0] && (opts.login = auth[0]); auth[1] && (opts.password = auth[1]); } if (url.pathname) { opts.vhost = unescape(url.pathname.substr(1)); } return opts; }; /* * * Connect helpers * */ // If you pass a array of hosts, lets choose a random host or the preferred host number, or then next one. Connection.prototype._chooseHost = function() { if(Array.isArray(this.options.host)){ if(this.hosti == null){ if(typeof this.options.hostPreference == 'number') { this.hosti = (this.options.hostPreference < this.options.host.length) ? this.options.hostPreference : this.options.host.length-1; } else { this.hosti = parseInt(Math.random() * this.options.host.length, 10); } } else { // If this is already set, it looks like we want to choose another one. // Add one to hosti but don't overflow it. this.hosti = (this.hosti + 1) % this.options.host.length; } return this.options.host[this.hosti]; } else { return this.options.host; } }; Connection.prototype._createSocket = function() { var hostName = this._chooseHost(), self = this; var options = { port: this.options.port, host: hostName }; var resetConnectionTimeout = function () { debug && debug('connected so resetting connection timeout'); this.setTimeout(0); }; // Connect socket if (this.options.ssl.enabled) { debug && debug('making ssl connection'); options = _.extend(options, this._getSSLOptions()); this.socket = tls.connect(options, resetConnectionTimeout); } else { debug && debug('making non-ssl connection'); this.socket = net.connect(options, resetConnectionTimeout); } var connTimeout = this.options.connectionTimeout; if (connTimeout) { debug && debug('setting connection timeout to ' + connTimeout); this.socket.setTimeout(connTimeout, function () { debug && debug('connection timeout'); this.destroy(); var e = new Error('connection timeout'); e.name = 'TimeoutError'; self.emit('error', e); }); } // Proxy events. // Note that if we don't attach a 'data' event, no data will flow. var events = ['close', 'connect', 'data', 'drain', 'error', 'end', 'secureConnect', 'timeout']; _.each(events, function(event){ self.socket.on(event, self.emit.bind(self, event)); }); // Proxy a few methods that we use / previously used. var methods = ['end', 'destroy', 'write', 'pause', 'resume', 'setEncoding', 'ref', 'unref', 'address']; _.each(methods, function(method){ self[method] = function(){ self.socket[method].apply(self.socket, arguments); }; }); }; Connection.prototype._getSSLOptions = function() { if (this.sslConnectionOptions) return this.sslConnectionOptions; this.sslConnectionOptions = {}; if (this.options.ssl.keyFile) { this.sslConnectionOptions.key = fs.readFileSync(this.options.ssl.keyFile); } if (this.options.ssl.certFile) { this.sslConnectionOptions.cert = fs.readFileSync(this.options.ssl.certFile); } if (this.options.ssl.caFile) { this.sslConnectionOptions.ca = fs.readFileSync(this.options.ssl.caFile); } this.sslConnectionOptions.rejectUnauthorized = this.options.ssl.rejectUnauthorized; return this.sslConnectionOptions; }; // Time to start the AMQP 7-way connection initialization handshake! // 1. The client sends the server a version string Connection.prototype._startHandshake = function() { debug && debug("Initiating handshake..."); this.write("AMQP" + String.fromCharCode(0,0,9,1)); }; /* * * Parse helpers * */ Connection.prototype._sendBody = function (channel, body, properties) { var r = this._bodyToBuffer(body); var props = r[0], buffer = r[1]; properties = _.extend(props || {}, properties); this._sendHeader(channel, buffer.length, properties); var pos = 0, len = buffer.length; var metaSize = 8; // headerBytes = 7, frameEndBytes = 1 var maxBodySize = maxFrameBuffer - metaSize; while (len > 0) { var bodySize = len < maxBodySize ? len : maxBodySize; var frameSize = bodySize + metaSize; var b = new Buffer(frameSize); b.used = 0; b[b.used++] = 3; // constants.frameBody serializer.serializeInt(b, 2, channel); serializer.serializeInt(b, 4, bodySize); buffer.copy(b, b.used, pos, pos+bodySize); b.used += bodySize; b[b.used++] = 206; // constants.frameEnd; this.write(b); len -= bodySize; pos += bodySize; } return; }; // connection: the connection // channel: the channel to send this on // size: size in bytes of the following message // properties: an object containing any of the following: // - contentType (default 'application/octet-stream') // - contentEncoding // - headers // - deliveryMode // - priority (0-9) // - correlationId // - replyTo // - expiration // - messageId // - timestamp // - userId // - appId // - clusterId Connection.prototype._sendHeader = function(channel, size, properties) { var b = new Buffer(maxFrameBuffer); // FIXME allocating too much. // use freelist? b.used = 0; var classInfo = classes[60]; // always basic class. // 7 OCTET FRAME HEADER b[b.used++] = 2; // constants.frameHeader serializer.serializeInt(b, 2, channel); var lengthStart = b.used; serializer.serializeInt(b, 4, 0 /*dummy*/); // length var bodyStart = b.used; // HEADER'S BODY serializer.serializeInt(b, 2, classInfo.index); // class 60 for Basic serializer.serializeInt(b, 2, 0); // weight, always 0 for rabbitmq serializer.serializeInt(b, 8, size); // byte size of body // properties - first propertyFlags properties = _.defaults(properties || {}, {contentType: 'application/octet-stream'}); var propertyFlags = 0; for (var i = 0; i < classInfo.fields.length; i++) { if (properties[classInfo.fields[i].name]) propertyFlags |= 1 << (15-i); } serializer.serializeInt(b, 2, propertyFlags); // now the actual properties. serializer.serializeFields(b, classInfo.fields, properties, false); //serializeTable(b, properties); var bodyEnd = b.used; // Go back to the header and write in the length now that we know it. b.used = lengthStart; serializer.serializeInt(b, 4, bodyEnd - bodyStart); b.used = bodyEnd; // 1 OCTET END b[b.used++] = 206; // constants.frameEnd; var s = new Buffer(b.used); b.copy(s); //debug && debug('header sent: ' + JSON.stringify(s)); this.write(s); }; Connection.prototype._sendMethod = function (channel, method, args) { debug && debug(channel + " < " + method.name + " " + JSON.stringify(args)); var b = this._sendBuffer; b.used = 0; b[b.used++] = 1; // constants.frameMethod serializer.serializeInt(b, 2, channel); var lengthIndex = b.used; serializer.serializeInt(b, 4, 42); // replace with actual length. var startIndex = b.used; serializer.serializeInt(b, 2, method.classIndex); // short, classId serializer.serializeInt(b, 2, method.methodIndex); // short, methodId serializer.serializeFields(b, method.fields, args, true); var endIndex = b.used; // write in the frame length now that we know it. b.used = lengthIndex; serializer.serializeInt(b, 4, endIndex - startIndex); b.used = endIndex; b[b.used++] = 206; // constants.frameEnd; var c = new Buffer(b.used); b.copy(c); debug && debug("sending frame: " + c.toJSON()); this.write(c); this._outboundHeartbeatTimerReset(); }; // tries to find the next available id slot for a channel Connection.prototype.generateChannelId = function () { // start from the last used slot id var channelId = this.channelCounter; while(true){ // use values in range of 1..65535 channelId = channelId % 65535 + 1; if(!this.channels[channelId]){ break; } // after a full loop throw an Error if(channelId == this.channelCounter){ throw new Error("No valid Channel Id values available"); } } this.channelCounter = channelId; return this.channelCounter; }; }).call(this,_dereq_("C:\\Users\\Alex\\AppData\\Roaming\\npm\\node_modules\\browserify\\node_modules\\insert-module-globals\\node_modules\\process\\browser.js"),_dereq_("buffer").Buffer) },{"../package":39,"./debug":30,"./definitions":31,"./exchange":32,"./parser":34,"./queue":36,"./serializer":37,"C:\\Users\\Alex\\AppData\\Roaming\\npm\\node_modules\\browserify\\node_modules\\insert-module-globals\\node_modules\\process\\browser.js":144,"buffer":128,"events":137,"fs":127,"lodash":38,"net":127,"tls":127,"url":157,"util":159}],29:[function(_dereq_,module,exports){ module.exports = { AMQPTypes: Object.freeze({ STRING: 'S'.charCodeAt(0) , INTEGER: 'I'.charCodeAt(0) , HASH: 'F'.charCodeAt(0) , TIME: 'T'.charCodeAt(0) , DECIMAL: 'D'.charCodeAt(0) , BOOLEAN: 't'.charCodeAt(0) , SIGNED_8BIT: 'b'.charCodeAt(0) , SIGNED_16BIT: 's'.charCodeAt(0) , SIGNED_64BIT: 'l'.charCodeAt(0) , _32BIT_FLOAT: 'f'.charCodeAt(0) , _64BIT_FLOAT: 'd'.charCodeAt(0) , VOID: 'v'.charCodeAt(0) , BYTE_ARRAY: 'x'.charCodeAt(0) , ARRAY: 'A'.charCodeAt(0) , TEN: '10'.charCodeAt(0) , BOOLEAN_TRUE: '\x01' , BOOLEAN_FALSE:'\x00' }) , Indicators: Object.freeze({ FRAME_END: 206 }) , FrameType: Object.freeze({ METHOD: 1 , HEADER: 2 , BODY: 3 , HEARTBEAT: 8 }) } },{}],30:[function(_dereq_,module,exports){ (function (process){ 'use strict'; var DEBUG = process.env['NODE_DEBUG_AMQP']; // only define debug function in debugging mode if (DEBUG) { module.exports = function debug () { console.error.apply(null, arguments); }; } else { module.exports = null; } }).call(this,_dereq_("C:\\Users\\Alex\\AppData\\Roaming\\npm\\node_modules\\browserify\\node_modules\\insert-module-globals\\node_modules\\process\\browser.js")) },{"C:\\Users\\Alex\\AppData\\Roaming\\npm\\node_modules\\browserify\\node_modules\\insert-module-globals\\node_modules\\process\\browser.js":144}],31:[function(_dereq_,module,exports){ 'use strict'; var protocol = _dereq_('./amqp-definitions-0-9-1'); // a look up table for methods recieved // indexed on class id, method id var methodTable = {}; // methods keyed on their name var methods = {}; // classes keyed on their index var classes = {}; (function () { // anon scope for init //debug("initializing amqp methods..."); for (var i = 0; i < protocol.classes.length; i++) { var classInfo = protocol.classes[i]; classes[classInfo.index] = classInfo; for (var j = 0; j < classInfo.methods.length; j++) { var methodInfo = classInfo.methods[j]; var name = classInfo.name + methodInfo.name[0].toUpperCase() + methodInfo.name.slice(1); //debug(name); var method = { name: name, fields: methodInfo.fields, methodIndex: methodInfo.index, classIndex: classInfo.index }; if (!methodTable[classInfo.index]) methodTable[classInfo.index] = {}; methodTable[classInfo.index][methodInfo.index] = method; methods[name] = method; } } })(); module.exports = {methods: methods, classes: classes, methodTable: methodTable}; },{"./amqp-definitions-0-9-1":26}],32:[function(_dereq_,module,exports){ 'use strict'; var events = _dereq_('events'); var util = _dereq_('util'); var net = _dereq_('net'); var tls = _dereq_('tls'); var fs = _dereq_('fs'); var _ = _dereq_('lodash'); var methods = _dereq_('./definitions').methods; var Channel = _dereq_('./channel'); var Exchange = module.exports = function Exchange (connection, channel, name, options, openCallback) { Channel.call(this, connection, channel); this.name = name; this.binds = 0; // keep track of queues bound this.exchangeBinds = 0; // keep track of exchanges bound this.sourceExchanges = {}; this.options = _.defaults(options || {}, {autoDelete: true}); this._openCallback = openCallback; this._sequence = null; this._unAcked = {}; this._addedExchangeErrorHandler = false; }; util.inherits(Exchange, Channel); // creates an error handler scoped to the given `exchange` function createExchangeErrorHandlerFor (exchange) { return function (err) { if (!exchange.options.confirm) return; for (var id in exchange._unAcked) { var task = exchange._unAcked[id]; task.emit('ack error', err); delete exchange._unAcked[id]; } } }; Exchange.prototype._onMethod = function (channel, method, args) { this.emit(method.name, args); if (this._handleTaskReply.apply(this, arguments)) return true; var cb; switch (method) { case methods.channelOpenOk: // Pre-baked exchanges don't need to be declared if (/^$|(amq\.)/.test(this.name)) { this.state = 'open'; // - issue #33 fix if (this._openCallback) { this._openCallback(this); this._openCallback = null; } // -- this.emit('open'); // For if we want to delete a exchange, // we dont care if all of the options match. } else if (this.options.noDeclare) { this.state = 'open'; if (this._openCallback) { this._openCallback(this); this._openCallback = null; } this.emit('open'); } else { this.connection._sendMethod(channel, methods.exchangeDeclare, { reserved1: 0 , reserved2: false , reserved3: false , exchange: this.name , type: this.options.type || 'topic' , passive: !!this.options.passive , durable: !!this.options.durable , autoDelete: !!this.options.autoDelete , internal: !!this.options.internal , noWait: false , "arguments":this.options.arguments || {} }); this.state = 'declaring'; } break; case methods.exchangeDeclareOk: if (this.options.confirm) { this.connection._sendMethod(channel, methods.confirmSelect, { noWait: false }); } else { this.state = 'open'; this.emit('open'); if (this._openCallback) { this._openCallback(this); this._openCallback = null; } } break; case methods.confirmSelectOk: this._sequence = 1; this.state = 'open'; this.emit('open'); if (this._openCallback) { this._openCallback(this); this._openCallback = null; } break; case methods.channelClose: this.state = "closed"; this.closeOK(); this.connection.exchangeClosed(this.name); var e = new Error(args.replyText); e.code = args.replyCode; this.emit('error', e); this.emit('close'); break; case methods.channelCloseOk: this.connection.exchangeClosed(this.name); this.emit('close'); break; case methods.basicAck: this.emit('basic-ack', args); var sequenceNumber = args.deliveryTag.readUInt32BE(4), tag; if (sequenceNumber === 0 && args.multiple === true) { // we must ack everything for (tag in this._unAcked) { this._unAcked[tag].emit('ack'); delete this._unAcked[tag]; } } else if (sequenceNumber !== 0 && args.multiple === true) { // we must ack everything before the delivery tag for (tag in this._unAcked) { if (tag <= sequenceNumber) { this._unAcked[tag].emit('ack'); delete this._unAcked[tag]; } } } else if (this._unAcked[sequenceNumber] && args.multiple === false) { // simple single ack this._unAcked[sequenceNumber].emit('ack'); delete this._unAcked[sequenceNumber]; } break; case methods.basicReturn: this.emit('basic-return', args); break; case methods.exchangeBindOk: if (this._bindCallback) { // setting this._bindCallback to null before calling the callback allows for a subsequent bind within the callback cb = this._bindCallback; this._bindCallback = null; cb(this); } break; case methods.exchangeUnbindOk: if (this._unbindCallback) { cb = this._unbindCallback; this._unbindCallback = null; cb(this); } break; default: throw new Error("Uncaught method '" + method.name + "' with args " + JSON.stringify(args)); } this._tasksFlush(); }; // exchange.publish('routing.key', 'body'); // // the third argument can specify additional options // - mandatory (boolean, default false) // - immediate (boolean, default false) // - contentType (default 'application/octet-stream') // - contentEncoding // - headers // - deliveryMode // - priority (0-9) // - correlationId // - replyTo // - expiration // - messageId // - timestamp // - userId // - appId // - clusterId // // the callback is optional and is only used when confirm is turned on for the exchange Exchange.prototype.publish = function (routingKey, data, options, callback) { var self = this; options = _.extend({}, options || {}); options.routingKey = routingKey; options.exchange = self.name; options.mandatory = options.mandatory ? true : false; options.immediate = options.immediate ? true : false; options.reserved1 = 0; var task = this._taskPush(null, function () { self.connection._sendMethod(self.channel, methods.basicPublish, options); // This interface is probably not appropriate for streaming large files. // (Of course it's arguable about whether AMQP is the appropriate // transport for large files.) The content header wants to know the size // of the data before sending it - so there's no point in trying to have a // general streaming interface - streaming messages of unknown size simply // isn't possible with AMQP. This is all to say, don't send big messages. // If you need to stream something large, chunk it yourself. self.connection._sendBody(self.channel, data, options); }); if (self.options.confirm) self._awaitConfirm(task, callback); return task; }; // registers tasks for confirms Exchange.prototype._awaitConfirm = function _awaitConfirm (task, callback) { if (!this._addedExchangeErrorHandler) { this.on('error', createExchangeErrorHandlerFor(this)); this._addedExchangeErrorHandler = true; } task.sequence = this._sequence; this._unAcked[this._sequence] = task; this._sequence++; if ('function' != typeof callback) return; task.once('ack error', function (err) { task.removeAllListeners(); callback(true, err); }); task.once('ack', function () { task.removeAllListeners(); callback(false); }); }; // do any necessary cleanups eg. after queue destruction Exchange.prototype.cleanup = function() { if (this.binds === 0) { // don't keep reference open if unused this.connection.exchangeClosed(this.name); } }; Exchange.prototype.destroy = function (ifUnused) { var self = this; return this._taskPush(methods.exchangeDeleteOk, function () { self.connection.exchangeClosed(self.name); self.connection._sendMethod(self.channel, methods.exchangeDelete, { reserved1: 0 , exchange: self.name , ifUnused: ifUnused ? true : false , noWait: false }); }); }; // E2E Unbind // support RabbitMQ's exchange-to-exchange binding extension // http://www.rabbitmq.com/e2e.html Exchange.prototype.unbind = function (/* exchange, routingKey [, bindCallback] */) { var self = this; // Both arguments are required. The binding to the destination // exchange/routingKey will be unbound. var exchange = arguments[0] , routingKey = arguments[1] , callback = arguments[2] ; if (callback) this._unbindCallback = callback; return this._taskPush(methods.exchangeUnbindOk, function () { var source = exchange instanceof Exchange ? exchange.name : exchange; var destination = self.name; if (source in self.connection.exchanges) { delete self.sourceExchanges[source]; self.connection.exchanges[source].exchangeBinds--; } self.connection._sendMethod(self.channel, methods.exchangeUnbind, { reserved1: 0 , destination: destination , source: source , routingKey: routingKey , noWait: false , "arguments": {} }); }); }; // E2E Bind // support RabbitMQ's exchange-to-exchange binding extension // http://www.rabbitmq.com/e2e.html Exchange.prototype.bind = function (/* exchange, routingKey [, bindCallback] */) { var self = this; // Two arguments are required. The binding to the destination // exchange/routingKey will be established. var exchange = arguments[0] , routingKey = arguments[1] , callback = arguments[2] ; if (callback) this._bindCallback = callback; var source = exchange instanceof Exchange ? exchange.name : exchange; var destination = self.name; if(source in self.connection.exchanges) { self.sourceExchanges[source] = self.connection.exchanges[source]; self.connection.exchanges[source].exchangeBinds++; } self.connection._sendMethod(self.channel, methods.exchangeBind, { reserved1: 0 , destination: destination , source: source , routingKey: routingKey , noWait: false , "arguments": {} }); }; // E2E Bind // support RabbitMQ's exchange-to-exchange binding extension // http://www.rabbitmq.com/e2e.html Exchange.prototype.bind_headers = function (/* exchange, routing [, bindCallback] */) { var self = this; // Two arguments are required. The binding to the destination // exchange/routingKey will be established. var exchange = arguments[0] , routing = arguments[1] , callback = arguments[2] ; if (callback) this._bindCallback = callback; var source = exchange instanceof Exchange ? exchange.name : exchange; var destination = self.name; if (source in self.connection.exchanges) { self.sourceExchanges[source] = self.connection.exchanges[source]; self.connection.exchanges[source].exchangeBinds++; } self.connection._sendMethod(self.channel, methods.exchangeBind, { reserved1: 0 , destination: destination , source: source , routingKey: '' , noWait: false , "arguments": routing }); }; },{"./channel":27,"./definitions":31,"events":137,"fs":127,"lodash":38,"net":127,"tls":127,"util":159}],33:[function(_dereq_,module,exports){ 'use strict'; var events = _dereq_('events'), util = _dereq_('util'), fs = _dereq_('fs'), protocol, definitions = _dereq_('./definitions'); // Properties: // - routingKey // - size // - deliveryTag // // - contentType (default 'application/octet-stream') // - contentEncoding // - headers // - deliveryMode // - priority (0-9) // - correlationId // - replyTo // - experation // - messageId // - timestamp // - userId // - appId // - clusterId var Message = module.exports = function Message (queue, args) { var msgProperties = definitions.classes[60].fields; events.EventEmitter.call(this); this.queue = queue; this.deliveryTag = args.deliveryTag; this.redelivered = args.redelivered; this.exchange = args.exchange; this.routingKey = args.routingKey; this.consumerTag = args.consumerTag; for (var i=0, l=msgProperties.length; i= fh.length) { fh.read = 0; frameType = fh[fh.read++]; frameChannel = parseInt(fh, 2); var frameSize = parseInt(fh, 4); fh.used = 0; // for reuse if (frameSize > maxFrameBuffer) { self.throwError("Oversized frame " + frameSize); } frameBuffer = new Buffer(frameSize); frameBuffer.used = 0; return frame(data.slice(needed)); } else { // need more! return header; } } function frame(data) { var fb = frameBuffer; var needed = fb.length - fb.used; var sourceEnd = (fb.length > data.length) ? data.length : fb.length; data.copy(fb, fb.used, 0, sourceEnd); fb.used += data.length; if (data.length > needed) { return frameEnd(data.slice(needed)); } else if (data.length == needed) { return frameEnd; } else { return frame; } } function frameEnd(data) { if (data.length > 0) { if (data[0] === Indicators.FRAME_END) { switch (frameType) { case FrameType.METHOD: self._parseMethodFrame(frameChannel, frameBuffer); break; case FrameType.HEADER: self._parseHeaderFrame(frameChannel, frameBuffer); break; case FrameType.BODY: if (self.onContent) { self.onContent(frameChannel, frameBuffer); } break; case FrameType.HEARTBEAT: debug && debug("heartbeat"); if (self.onHeartBeat) self.onHeartBeat(); break; default: self.throwError("Unhandled frame type " + frameType); break; } return header(data.slice(1)); } else { self.throwError("Missing frame end marker"); } } else { return frameEnd; } } self.parse = header; } // If there's an error in the parser, call the onError handler or throw AMQPParser.prototype.throwError = function (error) { if (this.onError) this.onError(error); else throw new Error(error); }; // Everytime data is recieved on the socket, pass it to this function for // parsing. AMQPParser.prototype.execute = function (data) { // This function only deals with dismantling and buffering the frames. // It delegates to other functions for parsing the frame-body. debug && debug('execute: ' + data.toString('hex')); this.parse = this.parse(data); }; // parse Network Byte Order integers. size can be 1,2,4,8 function parseInt (buffer, size) { switch (size) { case 1: return buffer[buffer.read++]; case 2: return (buffer[buffer.read++] << 8) + buffer[buffer.read++]; case 4: return (buffer[buffer.read++] << 24) + (buffer[buffer.read++] << 16) + (buffer[buffer.read++] << 8) + buffer[buffer.read++]; case 8: return (buffer[buffer.read++] << 56) + (buffer[buffer.read++] << 48) + (buffer[buffer.read++] << 40) + (buffer[buffer.read++] << 32) + (buffer[buffer.read++] << 24) + (buffer[buffer.read++] << 16) + (buffer[buffer.read++] << 8) + buffer[buffer.read++]; default: throw new Error("cannot parse ints of that size"); } } function parseShortString (buffer) { var length = buffer[buffer.read++]; var s = buffer.toString('utf8', buffer.read, buffer.read+length); buffer.read += length; return s; } function parseLongString (buffer) { var length = parseInt(buffer, 4); var s = buffer.slice(buffer.read, buffer.read + length); buffer.read += length; return s.toString(); } function parseSignedInteger (buffer) { var int = parseInt(buffer, 4); if (int & 0x80000000) { int |= 0xEFFFFFFF; int = -int; } return int; } function parseValue (buffer) { switch (buffer[buffer.read++]) { case AMQPTypes.STRING: return parseLongString(buffer); case AMQPTypes.INTEGER: return parseInt(buffer, 4); case AMQPTypes.DECIMAL: var dec = parseInt(buffer, 1); var num = parseInt(buffer, 4); return num / (dec * 10); case AMQPTypes._64BIT_FLOAT: var b = []; for (var i = 0; i < 8; ++i) b[i] = buffer[buffer.read++]; return (new jspack(true)).Unpack('d', b); case AMQPTypes._32BIT_FLOAT: var b = []; for (var i = 0; i < 4; ++i) b[i] = buffer[buffer.read++]; return (new jspack(true)).Unpack('f', b); case AMQPTypes.TIME: var int = parseInt(buffer, 8); return (new Date()).setTime(int * 1000); case AMQPTypes.HASH: return parseTable(buffer); case AMQPTypes.SIGNED_64BIT: return parseInt(buffer, 8); case AMQPTypes.BOOLEAN: return (parseInt(buffer, 1) > 0); case AMQPTypes.BYTE_ARRAY: var len = parseInt(buffer, 4); var buf = new Buffer(len); buffer.copy(buf, 0, buffer.read, buffer.read + len); buffer.read += len; return buf; case AMQPTypes.ARRAY: var len = parseInt(buffer, 4); var end = buffer.read + len; var arr = []; while (buffer.read < end) { arr.push(parseValue(buffer)); } return arr; default: throw new Error("Unknown field value type " + buffer[buffer.read-1]); } } function parseTable (buffer) { var length = buffer.read + parseInt(buffer, 4); var table = {}; while (buffer.read < length) { table[parseShortString(buffer)] = parseValue(buffer); } return table; } function parseFields (buffer, fields) { var args = {}; var bitIndex = 0; var value; for (var i = 0; i < fields.length; i++) { var field = fields[i]; //debug && debug("parsing field " + field.name + " of type " + field.domain); switch (field.domain) { case 'bit': // 8 bits can be packed into one octet. // XXX check if bitIndex greater than 7? value = (buffer[buffer.read] & (1 << bitIndex)) ? true : false; if (fields[i+1] && fields[i+1].domain == 'bit') { bitIndex++; } else { bitIndex = 0; buffer.read++; } break; case 'octet': value = buffer[buffer.read++]; break; case 'short': value = parseInt(buffer, 2); break; case 'long': value = parseInt(buffer, 4); break; // In a previous version this shared code with 'longlong', which caused problems when passed Date // integers. Nobody expects to pass a Buffer here, 53 bits is still 28 million years after 1970, we'll be fine. case 'timestamp': value = parseInt(buffer, 8); break; // JS doesn't support 64-bit Numbers, so we expect if you're using 'longlong' that you've // used a Buffer instead case 'longlong': value = new Buffer(8); for (var j = 0; j < 8; j++) { value[j] = buffer[buffer.read++]; } break; case 'shortstr': value = parseShortString(buffer); break; case 'longstr': value = parseLongString(buffer); break; case 'table': value = parseTable(buffer); break; default: throw new Error("Unhandled parameter type " + field.domain); } //debug && debug("got " + value); args[field.name] = value; } return args; } AMQPParser.prototype._parseMethodFrame = function (channel, buffer) { buffer.read = 0; var classId = parseInt(buffer, 2), methodId = parseInt(buffer, 2); // Make sure that this is a method that we understand. if (!methodTable[classId] || !methodTable[classId][methodId]) { this.throwError("Received unknown [classId, methodId] pair [" + classId + ", " + methodId + "]"); } var method = methodTable[classId][methodId]; if (!method) this.throwError("bad method?"); var args = parseFields(buffer, method.fields); if (this.onMethod) { debug && debug("Executing method", channel, method, args); this.onMethod(channel, method, args); } }; AMQPParser.prototype._parseHeaderFrame = function (channel, buffer) { buffer.read = 0; var classIndex = parseInt(buffer, 2); var weight = parseInt(buffer, 2); var size = parseInt(buffer, 8); var classInfo = classes[classIndex]; if (classInfo.fields.length > 15) { this.throwError("TODO: support more than 15 properties"); } var propertyFlags = parseInt(buffer, 2); var fields = []; for (var i = 0; i < classInfo.fields.length; i++) { var field = classInfo.fields[i]; // groan. if (propertyFlags & (1 << (15-i))) fields.push(field); } var properties = parseFields(buffer, fields); if (this.onContentHeader) { this.onContentHeader(channel, classInfo, weight, properties, size); } }; }).call(this,_dereq_("buffer").Buffer) },{"../jspack":25,"./constants":29,"./debug":30,"./definitions":31,"buffer":128,"events":137,"fs":127,"net":127,"tls":127,"util":159}],35:[function(_dereq_,module,exports){ (function (process){ var events = _dereq_('events'); var inherits = _dereq_('util').inherits; exports.Promise = function () { events.EventEmitter.call(this); this._blocking = false; this.hasFired = false; this.hasAcked = false; this._values = undefined; }; inherits(exports.Promise, events.EventEmitter); exports.Promise.prototype.timeout = function(timeout) { if (!timeout) { return this._timeoutDuration; } this._timeoutDuration = timeout; if (this.hasFired) return; this._clearTimeout(); var self = this; this._timer = setTimeout(function() { self._timer = null; if (self.hasFired) { return; } self.emitError(new Error('timeout')); }, timeout); return this; }; exports.Promise.prototype._clearTimeout = function() { if (!this._timer) return; clearTimeout(this._timer); this._timer = null; } exports.Promise.prototype.emitSuccess = function() { if (this.hasFired) return; this.hasFired = 'success'; this._clearTimeout(); this._values = Array.prototype.slice.call(arguments); this.emit.apply(this, ['success'].concat(this._values)); }; exports.Promise.prototype.emitError = function() { if (this.hasFired) return; this.hasFired = 'error'; this._clearTimeout(); this._values = Array.prototype.slice.call(arguments); this.emit.apply(this, ['error'].concat(this._values)); if (this.listeners('error').length == 0) { var self = this; process.nextTick(function() { if (self.listeners('error').length == 0) { throw (self._values[0] instanceof Error) ? self._values[0] : new Error('Unhandled emitError: '+JSON.stringify(self._values)); } }); } }; exports.Promise.prototype.addCallback = function (listener) { if (this.hasFired === 'success') { listener.apply(this, this._values); } return this.addListener("success", listener); }; exports.Promise.prototype.addErrback = function (listener) { if (this.hasFired === 'error') { listener.apply(this, this._values); } return this.addListener("error", listener); }; }).call(this,_dereq_("C:\\Users\\Alex\\AppData\\Roaming\\npm\\node_modules\\browserify\\node_modules\\insert-module-globals\\node_modules\\process\\browser.js")) },{"C:\\Users\\Alex\\AppData\\Roaming\\npm\\node_modules\\browserify\\node_modules\\insert-module-globals\\node_modules\\process\\browser.js":144,"events":137,"util":159}],36:[function(_dereq_,module,exports){ (function (process,Buffer){ 'use strict'; var util = _dereq_('util'); var fs = _dereq_('fs'); var _ = _dereq_('lodash'); var Channel = _dereq_('./channel'); var Exchange = _dereq_('./exchange'); var Message = _dereq_('./message'); var debug = _dereq_('./debug'); var definitions = _dereq_('./definitions'); var methods = definitions.methods; var classes = definitions.classes; var Queue = module.exports = function Queue (connection, channel, name, options, callback) { Channel.call(this, connection, channel); var self = this; this.name = name; this._bindings = {}; this.consumerTagListeners = {}; this.consumerTagOptions = {}; // route messages to subscribers based on consumerTag this.on('rawMessage', function(message) { if (message.consumerTag && self.consumerTagListeners[message.consumerTag]) { self.consumerTagListeners[message.consumerTag](message); } }); this.options = { autoDelete: true, closeChannelOnUnsubscribe: false }; _.extend(this.options, options || {}); this._openCallback = callback; }; util.inherits(Queue, Channel); Queue.prototype.subscribeRaw = function (options, messageListener) { var self = this; // multiple method signatures if (typeof options === "function") { messageListener = options; options = {}; } var consumerTag = 'node-amqp-' + process.pid + '-' + Math.random(); this.consumerTagListeners[consumerTag] = messageListener; options = options || {}; options['state'] = 'opening'; this.consumerTagOptions[consumerTag] = options; if (options.prefetchCount !== undefined) { self.connection._sendMethod(self.channel, methods.basicQos, { reserved1: 0 , prefetchSize: 0 , prefetchCount: options.prefetchCount , global: false }); } return this._taskPush(methods.basicConsumeOk, function () { self.connection._sendMethod(self.channel, methods.basicConsume, { reserved1: 0 , queue: self.name , consumerTag: consumerTag , noLocal: !!options.noLocal , noAck: !!options.noAck , exclusive: !!options.exclusive , noWait: false , "arguments": {} }); self.consumerTagOptions[consumerTag]['state'] = 'open'; }); }; Queue.prototype.unsubscribe = function(consumerTag) { var self = this; return this._taskPush(methods.basicCancelOk, function () { self.connection._sendMethod(self.channel, methods.basicCancel, { reserved1: 0, consumerTag: consumerTag, noWait: false }); }) .addCallback(function () { if (self.options.closeChannelOnUnsubscribe) { self.close(); } delete self.consumerTagListeners[consumerTag]; delete self.consumerTagOptions[consumerTag]; }); }; Queue.prototype.subscribe = function (options, messageListener) { var self = this; // Optional options if (typeof options === "function") { messageListener = options; options = {}; } options = _.defaults(options || {}, { ack: false, prefetchCount: 1, routingKeyInPayload: self.connection.options.routingKeyInPayload, deliveryTagInPayload: self.connection.options.deliveryTagInPayload }); // basic consume var rawOptions = { noAck: !options.ack, exclusive: options.exclusive }; if (options.ack) { rawOptions['prefetchCount'] = options.prefetchCount; } return this.subscribeRaw(rawOptions, function (m) { var contentType = m.contentType; if (contentType == null && m.headers && m.headers.properties) { contentType = m.headers.properties.content_type; } var isJSON = contentType == 'text/json' || contentType == 'application/json'; var buffer; if (isJSON) { buffer = ""; } else { buffer = new Buffer(m.size); buffer.used = 0; } self._lastMessage = m; m.addListener('data', function (d) { if (isJSON) { buffer += d.toString(); } else { d.copy(buffer, buffer.used); buffer.used += d.length; } }); m.addListener('end', function () { var json, deliveryInfo = {}, msgProperties = classes[60].fields, i, l; if (isJSON) { try { json = JSON.parse(buffer); } catch (e) { json = null; deliveryInfo.parseError = e; deliveryInfo.rawData = buffer; } } else { json = { data: buffer, contentType: m.contentType }; } for (i = 0, l = msgProperties.length; i b.length) { throw new Error("write out of bounds"); } // Only 4 cases - just going to be explicit instead of looping. switch (size) { // octet case 1: b[b.used++] = int; break; // short case 2: b[b.used++] = (int & 0xFF00) >> 8; b[b.used++] = (int & 0x00FF) >> 0; break; // long case 4: b[b.used++] = (int & 0xFF000000) >> 24; b[b.used++] = (int & 0x00FF0000) >> 16; b[b.used++] = (int & 0x0000FF00) >> 8; b[b.used++] = (int & 0x000000FF) >> 0; break; // long long case 8: b[b.used++] = (int & 0xFF00000000000000) >> 56; b[b.used++] = (int & 0x00FF000000000000) >> 48; b[b.used++] = (int & 0x0000FF0000000000) >> 40; b[b.used++] = (int & 0x000000FF00000000) >> 32; b[b.used++] = (int & 0x00000000FF000000) >> 24; b[b.used++] = (int & 0x0000000000FF0000) >> 16; b[b.used++] = (int & 0x000000000000FF00) >> 8; b[b.used++] = (int & 0x00000000000000FF) >> 0; break; default: throw new Error("Bad size"); } }, serializeShortString: function (b, string) { if (typeof(string) != "string") { throw new Error("param must be a string"); } var byteLength = Buffer.byteLength(string, 'utf8'); if (byteLength > 0xFF) { throw new Error("String too long for 'shortstr' parameter"); } if (1 + byteLength + b.used >= b.length) { throw new Error("Not enough space in buffer for 'shortstr'"); } b[b.used++] = byteLength; b.write(string, b.used, 'utf8'); b.used += byteLength; }, serializeLongString: function(b, string) { // we accept string, object, or buffer for this parameter. // in the case of string we serialize it to utf8. if (typeof(string) == 'string') { var byteLength = Buffer.byteLength(string, 'utf8'); serializer.serializeInt(b, 4, byteLength); b.write(string, b.used, 'utf8'); b.used += byteLength; } else if (typeof(string) == 'object') { serializer.serializeTable(b, string); } else { // data is Buffer var byteLength = string.length; serializer.serializeInt(b, 4, byteLength); b.write(string, b.used); // memcpy b.used += byteLength; } }, serializeDate: function(b, date) { serializer.serializeInt(b, 8, date.valueOf() / 1000); }, serializeBuffer: function(b, buffer) { serializer.serializeInt(b, 4, buffer.length); buffer.copy(b, b.used, 0); b.used += buffer.length; }, serializeBase64: function(b, buffer) { serializer.serializeLongString(b, buffer.toString('base64')); }, isBigInt: function(value) { return value > 0xffffffff; }, getCode: function(dec) { var hexArray = "0123456789ABCDEF".split(''); var code1 = Math.floor(dec / 16); var code2 = dec - code1 * 16; return hexArray[code2]; }, isFloat: function(value){ return value === +value && value !== (value|0); }, serializeValue: function(b, value) { switch (typeof(value)) { case 'string': b[b.used++] = 'S'.charCodeAt(0); serializer.serializeLongString(b, value); break; case 'number': if (!serializer.isFloat(value)) { if (serializer.isBigInt(value)) { // 64-bit uint b[b.used++] = 'l'.charCodeAt(0); serializer.serializeInt(b, 8, value); } else { //32-bit uint b[b.used++] = 'I'.charCodeAt(0); serializer.serializeInt(b, 4, value); } } else { //64-bit float b[b.used++] = 'd'.charCodeAt(0); serializer.serializeFloat(b, 8, value); } break; case 'boolean': b[b.used++] = 't'.charCodeAt(0); b[b.used++] = value; break; default: if (value instanceof Date) { b[b.used++] = 'T'.charCodeAt(0); serializer.serializeDate(b, value); } else if (value instanceof Buffer) { b[b.used++] = 'x'.charCodeAt(0); serializer.serializeBuffer(b, value); } else if (Array.isArray(value)) { b[b.used++] = 'A'.charCodeAt(0); serializer.serializeArray(b, value); } else if (typeof(value) === 'object') { b[b.used++] = 'F'.charCodeAt(0); serializer.serializeTable(b, value); } else { throw new Error("unsupported type in amqp table: " + typeof(value)); } } }, serializeTable: function(b, object) { if (typeof(object) != "object") { throw new Error("param must be an object"); } // Save our position so that we can go back and write the length of this table // at the beginning of the packet (once we know how many entries there are). var lengthIndex = b.used; b.used += 4; // sizeof long var startIndex = b.used; for (var key in object) { if (!object.hasOwnProperty(key)) continue; serializer.serializeShortString(b, key); serializer.serializeValue(b, object[key]); } var endIndex = b.used; b.used = lengthIndex; serializer.serializeInt(b, 4, endIndex - startIndex); b.used = endIndex; }, serializeArray: function(b, arr) { // Save our position so that we can go back and write the byte length of this array // at the beginning of the packet (once we have serialized all elements). var lengthIndex = b.used; b.used += 4; // sizeof long var startIndex = b.used; var len = arr.length; for (var i = 0; i < len; i++) { serializer.serializeValue(b, arr[i]); } var endIndex = b.used; b.used = lengthIndex; serializer.serializeInt(b, 4, endIndex - startIndex); b.used = endIndex; }, serializeFields: function(buffer, fields, args, strict) { var bitField = 0; var bitIndex = 0; for (var i = 0; i < fields.length; i++) { var field = fields[i]; var domain = field.domain; if (!(field.name in args)) { if (strict) { throw new Error("Missing field '" + field.name + "' of type '" + domain + "' while executing AMQP method '" + arguments.callee.caller.arguments[1].name + "'"); } continue; } var param = args[field.name]; //debug("domain: " + domain + " param: " + param); switch (domain) { case 'bit': if (typeof(param) != "boolean") { throw new Error("Unmatched field " + JSON.stringify(field)); } if (param) bitField |= (1 << bitIndex); bitIndex++; if (!fields[i+1] || fields[i+1].domain != 'bit') { //debug('SET bit field ' + field.name + ' 0x' + bitField.toString(16)); buffer[buffer.used++] = bitField; bitField = 0; bitIndex = 0; } break; case 'octet': if (typeof(param) != "number" || param > 0xFF) { throw new Error("Unmatched field " + JSON.stringify(field)); } buffer[buffer.used++] = param; break; case 'short': if (typeof(param) != "number" || param > 0xFFFF) { throw new Error("Unmatched field " + JSON.stringify(field)); } serializer.serializeInt(buffer, 2, param); break; case 'long': if (typeof(param) != "number" || param > 0xFFFFFFFF) { throw new Error("Unmatched field " + JSON.stringify(field)); } serializer.serializeInt(buffer, 4, param); break; // In a previous version this shared code with 'longlong', which caused problems when passed Date // integers. Nobody expects to pass a Buffer here, 53 bits is still 28 million years after 1970, we'll be fine. case 'timestamp': serializer.serializeInt(buffer, 8, param); break; case 'longlong': for (var j = 0; j < 8; j++) { buffer[buffer.used++] = param[j]; } break; case 'shortstr': if (typeof(param) != "string" || param.length > 0xFF) { throw new Error("Unmatched field " + JSON.stringify(field)); } serializer.serializeShortString(buffer, param); break; case 'longstr': serializer.serializeLongString(buffer, param); break; case 'table': if (typeof(param) != "object") { throw new Error("Unmatched field " + JSON.stringify(field)); } serializer.serializeTable(buffer, param); break; default: throw new Error("Unknown domain value type " + domain); } } } }; }).call(this,_dereq_("buffer").Buffer) },{"../jspack":25,"buffer":128}],38:[function(_dereq_,module,exports){ (function (global){ /** * @license * Lo-Dash 1.3.1 (Custom Build) * Build: `lodash modern -o ./dist/lodash.js` * Copyright 2012-2013 The Dojo Foundation * Based on Underscore.js 1.4.4 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. * Available under MIT license */ ;(function(window) { /** Used as a safe reference for `undefined` in pre ES5 environments */ var undefined; /** Used to pool arrays and objects used internally */ var arrayPool = [], objectPool = []; /** Used to generate unique IDs */ var idCounter = 0; /** Used internally to indicate various things */ var indicatorObject = {}; /** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */ var keyPrefix = +new Date + ''; /** Used as the size when optimizations are enabled for large arrays */ var largeArraySize = 75; /** Used as the max size of the `arrayPool` and `objectPool` */ var maxPoolSize = 40; /** Used to match empty string literals in compiled template source */ var reEmptyStringLeading = /\b__p \+= '';/g, reEmptyStringMiddle = /\b(__p \+=) '' \+/g, reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; /** Used to match HTML entities */ var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g; /** * Used to match ES6 template delimiters * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-7.8.6 */ var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; /** Used to match regexp flags from their coerced string values */ var reFlags = /\w*$/; /** Used to match "interpolate" template delimiters */ var reInterpolate = /<%=([\s\S]+?)%>/g; /** Used to detect functions containing a `this` reference */ var reThis = (reThis = /\bthis\b/) && reThis.test(runInContext) && reThis; /** Used to detect and test whitespace */ var whitespace = ( // whitespace ' \t\x0B\f\xA0\ufeff' + // line terminators '\n\r\u2028\u2029' + // unicode category "Zs" space separators '\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000' ); /** Used to match leading whitespace and zeros to be removed */ var reLeadingSpacesAndZeros = RegExp('^[' + whitespace + ']*0+(?=.$)'); /** Used to ensure capturing order of template delimiters */ var reNoMatch = /($^)/; /** Used to match HTML characters */ var reUnescapedHtml = /[&<>"']/g; /** Used to match unescaped characters in compiled string literals */ var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g; /** Used to assign default `context` object properties */ var contextProps = [ 'Array', 'Boolean', 'Date', 'Function', 'Math', 'Number', 'Object', 'RegExp', 'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN', 'parseInt', 'setImmediate', 'setTimeout' ]; /** Used to make template sourceURLs easier to identify */ var templateCounter = 0; /** `Object#toString` result shortcuts */ var argsClass = '[object Arguments]', arrayClass = '[object Array]', boolClass = '[object Boolean]', dateClass = '[object Date]', errorClass = '[object Error]', funcClass = '[object Function]', numberClass = '[object Number]', objectClass = '[object Object]', regexpClass = '[object RegExp]', stringClass = '[object String]'; /** Used to identify object classifications that `_.clone` supports */ var cloneableClasses = {}; cloneableClasses[funcClass] = false; cloneableClasses[argsClass] = cloneableClasses[arrayClass] = cloneableClasses[boolClass] = cloneableClasses[dateClass] = cloneableClasses[numberClass] = cloneableClasses[objectClass] = cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true; /** Used to determine if values are of the language type Object */ var objectTypes = { 'boolean': false, 'function': true, 'object': true, 'number': false, 'string': false, 'undefined': false }; /** Used to escape characters for inclusion in compiled string literals */ var stringEscapes = { '\\': '\\', "'": "'", '\n': 'n', '\r': 'r', '\t': 't', '\u2028': 'u2028', '\u2029': 'u2029' }; /** Detect free variable `exports` */ var freeExports = objectTypes[typeof exports] && exports; /** Detect free variable `module` */ var freeModule = objectTypes[typeof module] && module && module.exports == freeExports && module; /** Detect free variable `global`, from Node.js or Browserified code, and use it as `window` */ var freeGlobal = objectTypes[typeof global] && global; if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) { window = freeGlobal; } /*--------------------------------------------------------------------------*/ /** * A basic implementation of `_.indexOf` without support for binary searches * or `fromIndex` constraints. * * @private * @param {Array} array The array to search. * @param {Mixed} value The value to search for. * @param {Number} [fromIndex=0] The index to search from. * @returns {Number} Returns the index of the matched value or `-1`. */ function basicIndexOf(array, value, fromIndex) { var index = (fromIndex || 0) - 1, length = array.length; while (++index < length) { if (array[index] === value) { return index; } } return -1; } /** * An implementation of `_.contains` for cache objects that mimics the return * signature of `_.indexOf` by returning `0` if the value is found, else `-1`. * * @private * @param {Object} cache The cache object to inspect. * @param {Mixed} value The value to search for. * @returns {Number} Returns `0` if `value` is found, else `-1`. */ function cacheIndexOf(cache, value) { var type = typeof value; cache = cache.cache; if (type == 'boolean' || value == null) { return cache[value]; } if (type != 'number' && type != 'string') { type = 'object'; } var key = type == 'number' ? value : keyPrefix + value; cache = cache[type] || (cache[type] = {}); return type == 'object' ? (cache[key] && basicIndexOf(cache[key], value) > -1 ? 0 : -1) : (cache[key] ? 0 : -1); } /** * Adds a given `value` to the corresponding cache object. * * @private * @param {Mixed} value The value to add to the cache. */ function cachePush(value) { var cache = this.cache, type = typeof value; if (type == 'boolean' || value == null) { cache[value] = true; } else { if (type != 'number' && type != 'string') { type = 'object'; } var key = type == 'number' ? value : keyPrefix + value, typeCache = cache[type] || (cache[type] = {}); if (type == 'object') { if ((typeCache[key] || (typeCache[key] = [])).push(value) == this.array.length) { cache[type] = false; } } else { typeCache[key] = true; } } } /** * Used by `_.max` and `_.min` as the default `callback` when a given * `collection` is a string value. * * @private * @param {String} value The character to inspect. * @returns {Number} Returns the code unit of given character. */ function charAtCallback(value) { return value.charCodeAt(0); } /** * Used by `sortBy` to compare transformed `collection` values, stable sorting * them in ascending order. * * @private * @param {Object} a The object to compare to `b`. * @param {Object} b The object to compare to `a`. * @returns {Number} Returns the sort order indicator of `1` or `-1`. */ function compareAscending(a, b) { var ai = a.index, bi = b.index; a = a.criteria; b = b.criteria; // ensure a stable sort in V8 and other engines // http://code.google.com/p/v8/issues/detail?id=90 if (a !== b) { if (a > b || typeof a == 'undefined') { return 1; } if (a < b || typeof b == 'undefined') { return -1; } } return ai < bi ? -1 : 1; } /** * Creates a cache object to optimize linear searches of large arrays. * * @private * @param {Array} [array=[]] The array to search. * @returns {Null|Object} Returns the cache object or `null` if caching should not be used. */ function createCache(array) { var index = -1, length = array.length; var cache = getObject(); cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false; var result = getObject(); result.array = array; result.cache = cache; result.push = cachePush; while (++index < length) { result.push(array[index]); } return cache.object === false ? (releaseObject(result), null) : result; } /** * Used by `template` to escape characters for inclusion in compiled * string literals. * * @private * @param {String} match The matched character to escape. * @returns {String} Returns the escaped character. */ function escapeStringChar(match) { return '\\' + stringEscapes[match]; } /** * Gets an array from the array pool or creates a new one if the pool is empty. * * @private * @returns {Array} The array from the pool. */ function getArray() { return arrayPool.pop() || []; } /** * Gets an object from the object pool or creates a new one if the pool is empty. * * @private * @returns {Object} The object from the pool. */ function getObject() { return objectPool.pop() || { 'array': null, 'cache': null, 'criteria': null, 'false': false, 'index': 0, 'leading': false, 'maxWait': 0, 'null': false, 'number': null, 'object': null, 'push': null, 'string': null, 'trailing': false, 'true': false, 'undefined': false, 'value': null }; } /** * A no-operation function. * * @private */ function noop() { // no operation performed } /** * Releases the given `array` back to the array pool. * * @private * @param {Array} [array] The array to release. */ function releaseArray(array) { array.length = 0; if (arrayPool.length < maxPoolSize) { arrayPool.push(array); } } /** * Releases the given `object` back to the object pool. * * @private * @param {Object} [object] The object to release. */ function releaseObject(object) { var cache = object.cache; if (cache) { releaseObject(cache); } object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null; if (objectPool.length < maxPoolSize) { objectPool.push(object); } } /** * Slices the `collection` from the `start` index up to, but not including, * the `end` index. * * Note: This function is used, instead of `Array#slice`, to support node lists * in IE < 9 and to ensure dense arrays are returned. * * @private * @param {Array|Object|String} collection The collection to slice. * @param {Number} start The start index. * @param {Number} end The end index. * @returns {Array} Returns the new array. */ function slice(array, start, end) { start || (start = 0); if (typeof end == 'undefined') { end = array ? array.length : 0; } var index = -1, length = end - start || 0, result = Array(length < 0 ? 0 : length); while (++index < length) { result[index] = array[start + index]; } return result; } /*--------------------------------------------------------------------------*/ /** * Create a new `lodash` function using the given `context` object. * * @static * @memberOf _ * @category Utilities * @param {Object} [context=window] The context object. * @returns {Function} Returns the `lodash` function. */ function runInContext(context) { // Avoid issues with some ES3 environments that attempt to use values, named // after built-in constructors like `Object`, for the creation of literals. // ES5 clears this up by stating that literals must use built-in constructors. // See http://es5.github.com/#x11.1.5. context = context ? _.defaults(window.Object(), context, _.pick(window, contextProps)) : window; /** Native constructor references */ var Array = context.Array, Boolean = context.Boolean, Date = context.Date, Function = context.Function, Math = context.Math, Number = context.Number, Object = context.Object, RegExp = context.RegExp, String = context.String, TypeError = context.TypeError; /** * Used for `Array` method references. * * Normally `Array.prototype` would suffice, however, using an array literal * avoids issues in Narwhal. */ var arrayRef = []; /** Used for native method references */ var objectProto = Object.prototype, stringProto = String.prototype; /** Used to restore the original `_` reference in `noConflict` */ var oldDash = context._; /** Used to detect if a method is native */ var reNative = RegExp('^' + String(objectProto.valueOf) .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') .replace(/valueOf|for [^\]]+/g, '.+?') + '$' ); /** Native method shortcuts */ var ceil = Math.ceil, clearTimeout = context.clearTimeout, concat = arrayRef.concat, floor = Math.floor, fnToString = Function.prototype.toString, getPrototypeOf = reNative.test(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf, hasOwnProperty = objectProto.hasOwnProperty, push = arrayRef.push, propertyIsEnumerable = objectProto.propertyIsEnumerable, setImmediate = context.setImmediate, setTimeout = context.setTimeout, toString = objectProto.toString; /* Native method shortcuts for methods with the same name as other `lodash` methods */ var nativeBind = reNative.test(nativeBind = toString.bind) && nativeBind, nativeCreate = reNative.test(nativeCreate = Object.create) && nativeCreate, nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, nativeIsFinite = context.isFinite, nativeIsNaN = context.isNaN, nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys, nativeMax = Math.max, nativeMin = Math.min, nativeParseInt = context.parseInt, nativeRandom = Math.random, nativeSlice = arrayRef.slice; /** Detect various environments */ var isIeOpera = reNative.test(context.attachEvent), isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera); /** Used to lookup a built-in constructor by [[Class]] */ var ctorByClass = {}; ctorByClass[arrayClass] = Array; ctorByClass[boolClass] = Boolean; ctorByClass[dateClass] = Date; ctorByClass[funcClass] = Function; ctorByClass[objectClass] = Object; ctorByClass[numberClass] = Number; ctorByClass[regexpClass] = RegExp; ctorByClass[stringClass] = String; /*--------------------------------------------------------------------------*/ /** * Creates a `lodash` object, which wraps the given `value`, to enable method * chaining. * * In addition to Lo-Dash methods, wrappers also have the following `Array` methods: * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`, * and `unshift` * * Chaining is supported in custom builds as long as the `value` method is * implicitly or explicitly included in the build. * * The chainable wrapper functions are: * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, * `compose`, `concat`, `countBy`, `createCallback`, `debounce`, `defaults`, * `defer`, `delay`, `difference`, `filter`, `flatten`, `forEach`, `forIn`, * `forOwn`, `functions`, `groupBy`, `initial`, `intersection`, `invert`, * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`, * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `push`, `range`, * `reject`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, `sortBy`, `splice`, * `tap`, `throttle`, `times`, `toArray`, `transform`, `union`, `uniq`, `unshift`, * `unzip`, `values`, `where`, `without`, `wrap`, and `zip` * * The non-chainable wrapper functions are: * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `has`, * `identity`, `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, * `isElement`, `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, * `isNull`, `isNumber`, `isObject`, `isPlainObject`, `isRegExp`, `isString`, * `isUndefined`, `join`, `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, * `pop`, `random`, `reduce`, `reduceRight`, `result`, `shift`, `size`, `some`, * `sortedIndex`, `runInContext`, `template`, `unescape`, `uniqueId`, and `value` * * The wrapper functions `first` and `last` return wrapped values when `n` is * passed, otherwise they return unwrapped values. * * @name _ * @constructor * @alias chain * @category Chaining * @param {Mixed} value The value to wrap in a `lodash` instance. * @returns {Object} Returns a `lodash` instance. * @example * * var wrapped = _([1, 2, 3]); * * // returns an unwrapped value * wrapped.reduce(function(sum, num) { * return sum + num; * }); * // => 6 * * // returns a wrapped value * var squares = wrapped.map(function(num) { * return num * num; * }); * * _.isArray(squares); * // => false * * _.isArray(squares.value()); * // => true */ function lodash(value) { // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__')) ? value : new lodashWrapper(value); } /** * A fast path for creating `lodash` wrapper objects. * * @private * @param {Mixed} value The value to wrap in a `lodash` instance. * @returns {Object} Returns a `lodash` instance. */ function lodashWrapper(value) { this.__wrapped__ = value; } // ensure `new lodashWrapper` is an instance of `lodash` lodashWrapper.prototype = lodash.prototype; /** * An object used to flag environments features. * * @static * @memberOf _ * @type Object */ var support = lodash.support = {}; /** * Detect if `Function#bind` exists and is inferred to be fast (all but V8). * * @memberOf _.support * @type Boolean */ support.fastBind = nativeBind && !isV8; /** * By default, the template delimiters used by Lo-Dash are similar to those in * embedded Ruby (ERB). Change the following template settings to use alternative * delimiters. * * @static * @memberOf _ * @type Object */ lodash.templateSettings = { /** * Used to detect `data` property values to be HTML-escaped. * * @memberOf _.templateSettings * @type RegExp */ 'escape': /<%-([\s\S]+?)%>/g, /** * Used to detect code to be evaluated. * * @memberOf _.templateSettings * @type RegExp */ 'evaluate': /<%([\s\S]+?)%>/g, /** * Used to detect `data` property values to inject. * * @memberOf _.templateSettings * @type RegExp */ 'interpolate': reInterpolate, /** * Used to reference the data object in the template text. * * @memberOf _.templateSettings * @type String */ 'variable': '', /** * Used to import variables into the compiled template. * * @memberOf _.templateSettings * @type Object */ 'imports': { /** * A reference to the `lodash` function. * * @memberOf _.templateSettings.imports * @type Function */ '_': lodash } }; /*--------------------------------------------------------------------------*/ /** * Creates a function that, when called, invokes `func` with the `this` binding * of `thisArg` and prepends any `partialArgs` to the arguments passed to the * bound function. * * @private * @param {Function|String} func The function to bind or the method name. * @param {Mixed} [thisArg] The `this` binding of `func`. * @param {Array} partialArgs An array of arguments to be partially applied. * @param {Object} [idicator] Used to indicate binding by key or partially * applying arguments from the right. * @returns {Function} Returns the new bound function. */ function createBound(func, thisArg, partialArgs, indicator) { var isFunc = isFunction(func), isPartial = !partialArgs, key = thisArg; // juggle arguments if (isPartial) { var rightIndicator = indicator; partialArgs = thisArg; } else if (!isFunc) { if (!indicator) { throw new TypeError; } thisArg = func; } function bound() { // `Function#bind` spec // http://es5.github.com/#x15.3.4.5 var args = arguments, thisBinding = isPartial ? this : thisArg; if (!isFunc) { func = thisArg[key]; } if (partialArgs.length) { args = args.length ? (args = nativeSlice.call(args), rightIndicator ? args.concat(partialArgs) : partialArgs.concat(args)) : partialArgs; } if (this instanceof bound) { // ensure `new bound` is an instance of `func` thisBinding = createObject(func.prototype); // mimic the constructor's `return` behavior // http://es5.github.com/#x13.2.2 var result = func.apply(thisBinding, args); return isObject(result) ? result : thisBinding; } return func.apply(thisBinding, args); } return bound; } /** * Creates a new object with the specified `prototype`. * * @private * @param {Object} prototype The prototype object. * @returns {Object} Returns the new object. */ function createObject(prototype) { return isObject(prototype) ? nativeCreate(prototype) : {}; } /** * Used by `escape` to convert characters to HTML entities. * * @private * @param {String} match The matched character to escape. * @returns {String} Returns the escaped character. */ function escapeHtmlChar(match) { return htmlEscapes[match]; } /** * Gets the appropriate "indexOf" function. If the `_.indexOf` method is * customized, this method returns the custom method, otherwise it returns * the `basicIndexOf` function. * * @private * @returns {Function} Returns the "indexOf" function. */ function getIndexOf(array, value, fromIndex) { var result = (result = lodash.indexOf) === indexOf ? basicIndexOf : result; return result; } /** * Creates a function that juggles arguments, allowing argument overloading * for `_.flatten` and `_.uniq`, before passing them to the given `func`. * * @private * @param {Function} func The function to wrap. * @returns {Function} Returns the new function. */ function overloadWrapper(func) { return function(array, flag, callback, thisArg) { // juggle arguments if (typeof flag != 'boolean' && flag != null) { thisArg = callback; callback = !(thisArg && thisArg[flag] === array) ? flag : undefined; flag = false; } if (callback != null) { callback = lodash.createCallback(callback, thisArg); } return func(array, flag, callback, thisArg); }; } /** * A fallback implementation of `isPlainObject` which checks if a given `value` * is an object created by the `Object` constructor, assuming objects created * by the `Object` constructor have no inherited enumerable properties and that * there are no `Object.prototype` extensions. * * @private * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if `value` is a plain object, else `false`. */ function shimIsPlainObject(value) { var ctor, result; // avoid non Object objects, `arguments` objects, and DOM elements if (!(value && toString.call(value) == objectClass) || (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor))) { return false; } // In most environments an object's own properties are iterated before // its inherited properties. If the last iterated property is an object's // own property then there are no inherited enumerable properties. forIn(value, function(value, key) { result = key; }); return result === undefined || hasOwnProperty.call(value, result); } /** * Used by `unescape` to convert HTML entities to characters. * * @private * @param {String} match The matched character to unescape. * @returns {String} Returns the unescaped character. */ function unescapeHtmlChar(match) { return htmlUnescapes[match]; } /*--------------------------------------------------------------------------*/ /** * Checks if `value` is an `arguments` object. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is an `arguments` object, else `false`. * @example * * (function() { return _.isArguments(arguments); })(1, 2, 3); * // => true * * _.isArguments([1, 2, 3]); * // => false */ function isArguments(value) { return toString.call(value) == argsClass; } /** * Checks if `value` is an array. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is an array, else `false`. * @example * * (function() { return _.isArray(arguments); })(); * // => false * * _.isArray([1, 2, 3]); * // => true */ var isArray = nativeIsArray; /** * A fallback implementation of `Object.keys` which produces an array of the * given object's own enumerable property names. * * @private * @type Function * @param {Object} object The object to inspect. * @returns {Array} Returns a new array of property names. */ var shimKeys = function (object) { var index, iterable = object, result = []; if (!iterable) return result; if (!(objectTypes[typeof object])) return result; for (index in iterable) { if (hasOwnProperty.call(iterable, index)) { result.push(index); } } return result }; /** * Creates an array composed of the own enumerable property names of `object`. * * @static * @memberOf _ * @category Objects * @param {Object} object The object to inspect. * @returns {Array} Returns a new array of property names. * @example * * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); * // => ['one', 'two', 'three'] (order is not guaranteed) */ var keys = !nativeKeys ? shimKeys : function(object) { if (!isObject(object)) { return []; } return nativeKeys(object); }; /** * Used to convert characters to HTML entities: * * Though the `>` character is escaped for symmetry, characters like `>` and `/` * don't require escaping in HTML and have no special meaning unless they're part * of a tag or an unquoted attribute value. * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact") */ var htmlEscapes = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; /** Used to convert HTML entities to characters */ var htmlUnescapes = invert(htmlEscapes); /*--------------------------------------------------------------------------*/ /** * Assigns own enumerable properties of source object(s) to the destination * object. Subsequent sources will overwrite property assignments of previous * sources. If a `callback` function is passed, it will be executed to produce * the assigned values. The `callback` is bound to `thisArg` and invoked with * two arguments; (objectValue, sourceValue). * * @static * @memberOf _ * @type Function * @alias extend * @category Objects * @param {Object} object The destination object. * @param {Object} [source1, source2, ...] The source objects. * @param {Function} [callback] The function to customize assigning values. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns the destination object. * @example * * _.assign({ 'name': 'moe' }, { 'age': 40 }); * // => { 'name': 'moe', 'age': 40 } * * var defaults = _.partialRight(_.assign, function(a, b) { * return typeof a == 'undefined' ? b : a; * }); * * var food = { 'name': 'apple' }; * defaults(food, { 'name': 'banana', 'type': 'fruit' }); * // => { 'name': 'apple', 'type': 'fruit' } */ var assign = function (object, source, guard) { var index, iterable = object, result = iterable; if (!iterable) return result; var args = arguments, argsIndex = 0, argsLength = typeof guard == 'number' ? 2 : args.length; if (argsLength > 3 && typeof args[argsLength - 2] == 'function') { var callback = lodash.createCallback(args[--argsLength - 1], args[argsLength--], 2); } else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') { callback = args[--argsLength]; } while (++argsIndex < argsLength) { iterable = args[argsIndex]; if (iterable && objectTypes[typeof iterable]) { var ownIndex = -1, ownProps = objectTypes[typeof iterable] && keys(iterable), length = ownProps ? ownProps.length : 0; while (++ownIndex < length) { index = ownProps[ownIndex]; result[index] = callback ? callback(result[index], iterable[index]) : iterable[index]; } } } return result }; /** * Creates a clone of `value`. If `deep` is `true`, nested objects will also * be cloned, otherwise they will be assigned by reference. If a `callback` * function is passed, it will be executed to produce the cloned values. If * `callback` returns `undefined`, cloning will be handled by the method instead. * The `callback` is bound to `thisArg` and invoked with one argument; (value). * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to clone. * @param {Boolean} [deep=false] A flag to indicate a deep clone. * @param {Function} [callback] The function to customize cloning values. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @param- {Array} [stackA=[]] Tracks traversed source objects. * @param- {Array} [stackB=[]] Associates clones with source counterparts. * @returns {Mixed} Returns the cloned `value`. * @example * * var stooges = [ * { 'name': 'moe', 'age': 40 }, * { 'name': 'larry', 'age': 50 } * ]; * * var shallow = _.clone(stooges); * shallow[0] === stooges[0]; * // => true * * var deep = _.clone(stooges, true); * deep[0] === stooges[0]; * // => false * * _.mixin({ * 'clone': _.partialRight(_.clone, function(value) { * return _.isElement(value) ? value.cloneNode(false) : undefined; * }) * }); * * var clone = _.clone(document.body); * clone.childNodes.length; * // => 0 */ function clone(value, deep, callback, thisArg, stackA, stackB) { var result = value; // allows working with "Collections" methods without using their `callback` // argument, `index|key`, for this method's `callback` if (typeof deep != 'boolean' && deep != null) { thisArg = callback; callback = deep; deep = false; } if (typeof callback == 'function') { callback = (typeof thisArg == 'undefined') ? callback : lodash.createCallback(callback, thisArg, 1); result = callback(result); if (typeof result != 'undefined') { return result; } result = value; } // inspect [[Class]] var isObj = isObject(result); if (isObj) { var className = toString.call(result); if (!cloneableClasses[className]) { return result; } var isArr = isArray(result); } // shallow clone if (!isObj || !deep) { return isObj ? (isArr ? slice(result) : assign({}, result)) : result; } var ctor = ctorByClass[className]; switch (className) { case boolClass: case dateClass: return new ctor(+result); case numberClass: case stringClass: return new ctor(result); case regexpClass: return ctor(result.source, reFlags.exec(result)); } // check for circular references and return corresponding clone var initedStack = !stackA; stackA || (stackA = getArray()); stackB || (stackB = getArray()); var length = stackA.length; while (length--) { if (stackA[length] == value) { return stackB[length]; } } // init cloned object result = isArr ? ctor(result.length) : {}; // add array properties assigned by `RegExp#exec` if (isArr) { if (hasOwnProperty.call(value, 'index')) { result.index = value.index; } if (hasOwnProperty.call(value, 'input')) { result.input = value.input; } } // add the source value to the stack of traversed objects // and associate it with its clone stackA.push(value); stackB.push(result); // recursively populate clone (susceptible to call stack limits) (isArr ? forEach : forOwn)(value, function(objValue, key) { result[key] = clone(objValue, deep, callback, undefined, stackA, stackB); }); if (initedStack) { releaseArray(stackA); releaseArray(stackB); } return result; } /** * Creates a deep clone of `value`. If a `callback` function is passed, * it will be executed to produce the cloned values. If `callback` returns * `undefined`, cloning will be handled by the method instead. The `callback` * is bound to `thisArg` and invoked with one argument; (value). * * Note: This method is loosely based on the structured clone algorithm. Functions * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and * objects created by constructors other than `Object` are cloned to plain `Object` objects. * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to deep clone. * @param {Function} [callback] The function to customize cloning values. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the deep cloned `value`. * @example * * var stooges = [ * { 'name': 'moe', 'age': 40 }, * { 'name': 'larry', 'age': 50 } * ]; * * var deep = _.cloneDeep(stooges); * deep[0] === stooges[0]; * // => false * * var view = { * 'label': 'docs', * 'node': element * }; * * var clone = _.cloneDeep(view, function(value) { * return _.isElement(value) ? value.cloneNode(true) : undefined; * }); * * clone.node == view.node; * // => false */ function cloneDeep(value, callback, thisArg) { return clone(value, true, callback, thisArg); } /** * Assigns own enumerable properties of source object(s) to the destination * object for all destination properties that resolve to `undefined`. Once a * property is set, additional defaults of the same property will be ignored. * * @static * @memberOf _ * @type Function * @category Objects * @param {Object} object The destination object. * @param {Object} [source1, source2, ...] The source objects. * @param- {Object} [guard] Allows working with `_.reduce` without using its * callback's `key` and `object` arguments as sources. * @returns {Object} Returns the destination object. * @example * * var food = { 'name': 'apple' }; * _.defaults(food, { 'name': 'banana', 'type': 'fruit' }); * // => { 'name': 'apple', 'type': 'fruit' } */ var defaults = function (object, source, guard) { var index, iterable = object, result = iterable; if (!iterable) return result; var args = arguments, argsIndex = 0, argsLength = typeof guard == 'number' ? 2 : args.length; while (++argsIndex < argsLength) { iterable = args[argsIndex]; if (iterable && objectTypes[typeof iterable]) { var ownIndex = -1, ownProps = objectTypes[typeof iterable] && keys(iterable), length = ownProps ? ownProps.length : 0; while (++ownIndex < length) { index = ownProps[ownIndex]; if (typeof result[index] == 'undefined') result[index] = iterable[index]; } } } return result }; /** * This method is similar to `_.find`, except that it returns the key of the * element that passes the callback check, instead of the element itself. * * @static * @memberOf _ * @category Objects * @param {Object} object The object to search. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the key of the found element, else `undefined`. * @example * * _.findKey({ 'a': 1, 'b': 2, 'c': 3, 'd': 4 }, function(num) { * return num % 2 == 0; * }); * // => 'b' */ function findKey(object, callback, thisArg) { var result; callback = lodash.createCallback(callback, thisArg); forOwn(object, function(value, key, object) { if (callback(value, key, object)) { result = key; return false; } }); return result; } /** * Iterates over `object`'s own and inherited enumerable properties, executing * the `callback` for each property. The `callback` is bound to `thisArg` and * invoked with three arguments; (value, key, object). Callbacks may exit iteration * early by explicitly returning `false`. * * @static * @memberOf _ * @type Function * @category Objects * @param {Object} object The object to iterate over. * @param {Function} [callback=identity] The function called per iteration. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns `object`. * @example * * function Dog(name) { * this.name = name; * } * * Dog.prototype.bark = function() { * alert('Woof, woof!'); * }; * * _.forIn(new Dog('Dagny'), function(value, key) { * alert(key); * }); * // => alerts 'name' and 'bark' (order is not guaranteed) */ var forIn = function (collection, callback, thisArg) { var index, iterable = collection, result = iterable; if (!iterable) return result; if (!objectTypes[typeof iterable]) return result; callback = callback && typeof thisArg == 'undefined' ? callback : lodash.createCallback(callback, thisArg); for (index in iterable) { if (callback(iterable[index], index, collection) === false) return result; } return result }; /** * Iterates over an object's own enumerable properties, executing the `callback` * for each property. The `callback` is bound to `thisArg` and invoked with three * arguments; (value, key, object). Callbacks may exit iteration early by explicitly * returning `false`. * * @static * @memberOf _ * @type Function * @category Objects * @param {Object} object The object to iterate over. * @param {Function} [callback=identity] The function called per iteration. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns `object`. * @example * * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { * alert(key); * }); * // => alerts '0', '1', and 'length' (order is not guaranteed) */ var forOwn = function (collection, callback, thisArg) { var index, iterable = collection, result = iterable; if (!iterable) return result; if (!objectTypes[typeof iterable]) return result; callback = callback && typeof thisArg == 'undefined' ? callback : lodash.createCallback(callback, thisArg); var ownIndex = -1, ownProps = objectTypes[typeof iterable] && keys(iterable), length = ownProps ? ownProps.length : 0; while (++ownIndex < length) { index = ownProps[ownIndex]; if (callback(iterable[index], index, collection) === false) return result; } return result }; /** * Creates a sorted array of all enumerable properties, own and inherited, * of `object` that have function values. * * @static * @memberOf _ * @alias methods * @category Objects * @param {Object} object The object to inspect. * @returns {Array} Returns a new array of property names that have function values. * @example * * _.functions(_); * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] */ function functions(object) { var result = []; forIn(object, function(value, key) { if (isFunction(value)) { result.push(key); } }); return result.sort(); } /** * Checks if the specified object `property` exists and is a direct property, * instead of an inherited property. * * @static * @memberOf _ * @category Objects * @param {Object} object The object to check. * @param {String} property The property to check for. * @returns {Boolean} Returns `true` if key is a direct property, else `false`. * @example * * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); * // => true */ function has(object, property) { return object ? hasOwnProperty.call(object, property) : false; } /** * Creates an object composed of the inverted keys and values of the given `object`. * * @static * @memberOf _ * @category Objects * @param {Object} object The object to invert. * @returns {Object} Returns the created inverted object. * @example * * _.invert({ 'first': 'moe', 'second': 'larry' }); * // => { 'moe': 'first', 'larry': 'second' } */ function invert(object) { var index = -1, props = keys(object), length = props.length, result = {}; while (++index < length) { var key = props[index]; result[object[key]] = key; } return result; } /** * Checks if `value` is a boolean value. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is a boolean value, else `false`. * @example * * _.isBoolean(null); * // => false */ function isBoolean(value) { return value === true || value === false || toString.call(value) == boolClass; } /** * Checks if `value` is a date. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is a date, else `false`. * @example * * _.isDate(new Date); * // => true */ function isDate(value) { return value ? (typeof value == 'object' && toString.call(value) == dateClass) : false; } /** * Checks if `value` is a DOM element. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is a DOM element, else `false`. * @example * * _.isElement(document.body); * // => true */ function isElement(value) { return value ? value.nodeType === 1 : false; } /** * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a * length of `0` and objects with no own enumerable properties are considered * "empty". * * @static * @memberOf _ * @category Objects * @param {Array|Object|String} value The value to inspect. * @returns {Boolean} Returns `true`, if the `value` is empty, else `false`. * @example * * _.isEmpty([1, 2, 3]); * // => false * * _.isEmpty({}); * // => true * * _.isEmpty(''); * // => true */ function isEmpty(value) { var result = true; if (!value) { return result; } var className = toString.call(value), length = value.length; if ((className == arrayClass || className == stringClass || className == argsClass ) || (className == objectClass && typeof length == 'number' && isFunction(value.splice))) { return !length; } forOwn(value, function() { return (result = false); }); return result; } /** * Performs a deep comparison between two values to determine if they are * equivalent to each other. If `callback` is passed, it will be executed to * compare values. If `callback` returns `undefined`, comparisons will be handled * by the method instead. The `callback` is bound to `thisArg` and invoked with * two arguments; (a, b). * * @static * @memberOf _ * @category Objects * @param {Mixed} a The value to compare. * @param {Mixed} b The other value to compare. * @param {Function} [callback] The function to customize comparing values. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @param- {Array} [stackA=[]] Tracks traversed `a` objects. * @param- {Array} [stackB=[]] Tracks traversed `b` objects. * @returns {Boolean} Returns `true`, if the values are equivalent, else `false`. * @example * * var moe = { 'name': 'moe', 'age': 40 }; * var copy = { 'name': 'moe', 'age': 40 }; * * moe == copy; * // => false * * _.isEqual(moe, copy); * // => true * * var words = ['hello', 'goodbye']; * var otherWords = ['hi', 'goodbye']; * * _.isEqual(words, otherWords, function(a, b) { * var reGreet = /^(?:hello|hi)$/i, * aGreet = _.isString(a) && reGreet.test(a), * bGreet = _.isString(b) && reGreet.test(b); * * return (aGreet || bGreet) ? (aGreet == bGreet) : undefined; * }); * // => true */ function isEqual(a, b, callback, thisArg, stackA, stackB) { // used to indicate that when comparing objects, `a` has at least the properties of `b` var whereIndicator = callback === indicatorObject; if (typeof callback == 'function' && !whereIndicator) { callback = lodash.createCallback(callback, thisArg, 2); var result = callback(a, b); if (typeof result != 'undefined') { return !!result; } } // exit early for identical values if (a === b) { // treat `+0` vs. `-0` as not equal return a !== 0 || (1 / a == 1 / b); } var type = typeof a, otherType = typeof b; // exit early for unlike primitive values if (a === a && (!a || (type != 'function' && type != 'object')) && (!b || (otherType != 'function' && otherType != 'object'))) { return false; } // exit early for `null` and `undefined`, avoiding ES3's Function#call behavior // http://es5.github.com/#x15.3.4.4 if (a == null || b == null) { return a === b; } // compare [[Class]] names var className = toString.call(a), otherClass = toString.call(b); if (className == argsClass) { className = objectClass; } if (otherClass == argsClass) { otherClass = objectClass; } if (className != otherClass) { return false; } switch (className) { case boolClass: case dateClass: // coerce dates and booleans to numbers, dates to milliseconds and booleans // to `1` or `0`, treating invalid dates coerced to `NaN` as not equal return +a == +b; case numberClass: // treat `NaN` vs. `NaN` as equal return (a != +a) ? b != +b // but treat `+0` vs. `-0` as not equal : (a == 0 ? (1 / a == 1 / b) : a == +b); case regexpClass: case stringClass: // coerce regexes to strings (http://es5.github.com/#x15.10.6.4) // treat string primitives and their corresponding object instances as equal return a == String(b); } var isArr = className == arrayClass; if (!isArr) { // unwrap any `lodash` wrapped values if (hasOwnProperty.call(a, '__wrapped__ ') || hasOwnProperty.call(b, '__wrapped__')) { return isEqual(a.__wrapped__ || a, b.__wrapped__ || b, callback, thisArg, stackA, stackB); } // exit for functions and DOM nodes if (className != objectClass) { return false; } // in older versions of Opera, `arguments` objects have `Array` constructors var ctorA = a.constructor, ctorB = b.constructor; // non `Object` object instances with different constructors are not equal if (ctorA != ctorB && !( isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB )) { return false; } } // assume cyclic structures are equal // the algorithm for detecting cyclic structures is adapted from ES 5.1 // section 15.12.3, abstract operation `JO` (http://es5.github.com/#x15.12.3) var initedStack = !stackA; stackA || (stackA = getArray()); stackB || (stackB = getArray()); var length = stackA.length; while (length--) { if (stackA[length] == a) { return stackB[length] == b; } } var size = 0; result = true; // add `a` and `b` to the stack of traversed objects stackA.push(a); stackB.push(b); // recursively compare objects and arrays (susceptible to call stack limits) if (isArr) { length = a.length; size = b.length; // compare lengths to determine if a deep comparison is necessary result = size == a.length; if (!result && !whereIndicator) { return result; } // deep compare the contents, ignoring non-numeric properties while (size--) { var index = length, value = b[size]; if (whereIndicator) { while (index--) { if ((result = isEqual(a[index], value, callback, thisArg, stackA, stackB))) { break; } } } else if (!(result = isEqual(a[size], value, callback, thisArg, stackA, stackB))) { break; } } return result; } // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys` // which, in this case, is more costly forIn(b, function(value, key, b) { if (hasOwnProperty.call(b, key)) { // count the number of properties. size++; // deep compare each property value. return (result = hasOwnProperty.call(a, key) && isEqual(a[key], value, callback, thisArg, stackA, stackB)); } }); if (result && !whereIndicator) { // ensure both objects have the same number of properties forIn(a, function(value, key, a) { if (hasOwnProperty.call(a, key)) { // `size` will be `-1` if `a` has more properties than `b` return (result = --size > -1); } }); } if (initedStack) { releaseArray(stackA); releaseArray(stackB); } return result; } /** * Checks if `value` is, or can be coerced to, a finite number. * * Note: This is not the same as native `isFinite`, which will return true for * booleans and empty strings. See http://es5.github.com/#x15.1.2.5. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is finite, else `false`. * @example * * _.isFinite(-101); * // => true * * _.isFinite('10'); * // => true * * _.isFinite(true); * // => false * * _.isFinite(''); * // => false * * _.isFinite(Infinity); * // => false */ function isFinite(value) { return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value)); } /** * Checks if `value` is a function. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is a function, else `false`. * @example * * _.isFunction(_); * // => true */ function isFunction(value) { return typeof value == 'function'; } /** * Checks if `value` is the language type of Object. * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is an object, else `false`. * @example * * _.isObject({}); * // => true * * _.isObject([1, 2, 3]); * // => true * * _.isObject(1); * // => false */ function isObject(value) { // check if the value is the ECMAScript language type of Object // http://es5.github.com/#x8 // and avoid a V8 bug // http://code.google.com/p/v8/issues/detail?id=2291 return !!(value && objectTypes[typeof value]); } /** * Checks if `value` is `NaN`. * * Note: This is not the same as native `isNaN`, which will return `true` for * `undefined` and other values. See http://es5.github.com/#x15.1.2.4. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is `NaN`, else `false`. * @example * * _.isNaN(NaN); * // => true * * _.isNaN(new Number(NaN)); * // => true * * isNaN(undefined); * // => true * * _.isNaN(undefined); * // => false */ function isNaN(value) { // `NaN` as a primitive is the only value that is not equal to itself // (perform the [[Class]] check first to avoid errors with some host objects in IE) return isNumber(value) && value != +value } /** * Checks if `value` is `null`. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is `null`, else `false`. * @example * * _.isNull(null); * // => true * * _.isNull(undefined); * // => false */ function isNull(value) { return value === null; } /** * Checks if `value` is a number. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is a number, else `false`. * @example * * _.isNumber(8.4 * 5); * // => true */ function isNumber(value) { return typeof value == 'number' || toString.call(value) == numberClass; } /** * Checks if a given `value` is an object created by the `Object` constructor. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if `value` is a plain object, else `false`. * @example * * function Stooge(name, age) { * this.name = name; * this.age = age; * } * * _.isPlainObject(new Stooge('moe', 40)); * // => false * * _.isPlainObject([1, 2, 3]); * // => false * * _.isPlainObject({ 'name': 'moe', 'age': 40 }); * // => true */ var isPlainObject = function(value) { if (!(value && toString.call(value) == objectClass)) { return false; } var valueOf = value.valueOf, objProto = typeof valueOf == 'function' && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto); return objProto ? (value == objProto || getPrototypeOf(value) == objProto) : shimIsPlainObject(value); }; /** * Checks if `value` is a regular expression. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is a regular expression, else `false`. * @example * * _.isRegExp(/moe/); * // => true */ function isRegExp(value) { return value ? (typeof value == 'object' && toString.call(value) == regexpClass) : false; } /** * Checks if `value` is a string. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is a string, else `false`. * @example * * _.isString('moe'); * // => true */ function isString(value) { return typeof value == 'string' || toString.call(value) == stringClass; } /** * Checks if `value` is `undefined`. * * @static * @memberOf _ * @category Objects * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true`, if the `value` is `undefined`, else `false`. * @example * * _.isUndefined(void 0); * // => true */ function isUndefined(value) { return typeof value == 'undefined'; } /** * Recursively merges own enumerable properties of the source object(s), that * don't resolve to `undefined`, into the destination object. Subsequent sources * will overwrite property assignments of previous sources. If a `callback` function * is passed, it will be executed to produce the merged values of the destination * and source properties. If `callback` returns `undefined`, merging will be * handled by the method instead. The `callback` is bound to `thisArg` and * invoked with two arguments; (objectValue, sourceValue). * * @static * @memberOf _ * @category Objects * @param {Object} object The destination object. * @param {Object} [source1, source2, ...] The source objects. * @param {Function} [callback] The function to customize merging properties. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @param- {Object} [deepIndicator] Indicates that `stackA` and `stackB` are * arrays of traversed objects, instead of source objects. * @param- {Array} [stackA=[]] Tracks traversed source objects. * @param- {Array} [stackB=[]] Associates values with source counterparts. * @returns {Object} Returns the destination object. * @example * * var names = { * 'stooges': [ * { 'name': 'moe' }, * { 'name': 'larry' } * ] * }; * * var ages = { * 'stooges': [ * { 'age': 40 }, * { 'age': 50 } * ] * }; * * _.merge(names, ages); * // => { 'stooges': [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] } * * var food = { * 'fruits': ['apple'], * 'vegetables': ['beet'] * }; * * var otherFood = { * 'fruits': ['banana'], * 'vegetables': ['carrot'] * }; * * _.merge(food, otherFood, function(a, b) { * return _.isArray(a) ? a.concat(b) : undefined; * }); * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] } */ function merge(object, source, deepIndicator) { var args = arguments, index = 0, length = 2; if (!isObject(object)) { return object; } if (deepIndicator === indicatorObject) { var callback = args[3], stackA = args[4], stackB = args[5]; } else { var initedStack = true; stackA = getArray(); stackB = getArray(); // allows working with `_.reduce` and `_.reduceRight` without // using their `callback` arguments, `index|key` and `collection` if (typeof deepIndicator != 'number') { length = args.length; } if (length > 3 && typeof args[length - 2] == 'function') { callback = lodash.createCallback(args[--length - 1], args[length--], 2); } else if (length > 2 && typeof args[length - 1] == 'function') { callback = args[--length]; } } while (++index < length) { (isArray(args[index]) ? forEach : forOwn)(args[index], function(source, key) { var found, isArr, result = source, value = object[key]; if (source && ((isArr = isArray(source)) || isPlainObject(source))) { // avoid merging previously merged cyclic sources var stackLength = stackA.length; while (stackLength--) { if ((found = stackA[stackLength] == source)) { value = stackB[stackLength]; break; } } if (!found) { var isShallow; if (callback) { result = callback(value, source); if ((isShallow = typeof result != 'undefined')) { value = result; } } if (!isShallow) { value = isArr ? (isArray(value) ? value : []) : (isPlainObject(value) ? value : {}); } // add `source` and associated `value` to the stack of traversed objects stackA.push(source); stackB.push(value); // recursively merge objects and arrays (susceptible to call stack limits) if (!isShallow) { value = merge(value, source, indicatorObject, callback, stackA, stackB); } } } else { if (callback) { result = callback(value, source); if (typeof result == 'undefined') { result = source; } } if (typeof result != 'undefined') { value = result; } } object[key] = value; }); } if (initedStack) { releaseArray(stackA); releaseArray(stackB); } return object; } /** * Creates a shallow clone of `object` excluding the specified properties. * Property names may be specified as individual arguments or as arrays of * property names. If a `callback` function is passed, it will be executed * for each property in the `object`, omitting the properties `callback` * returns truthy for. The `callback` is bound to `thisArg` and invoked * with three arguments; (value, key, object). * * @static * @memberOf _ * @category Objects * @param {Object} object The source object. * @param {Function|String} callback|[prop1, prop2, ...] The properties to omit * or the function called per iteration. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns an object without the omitted properties. * @example * * _.omit({ 'name': 'moe', 'age': 40 }, 'age'); * // => { 'name': 'moe' } * * _.omit({ 'name': 'moe', 'age': 40 }, function(value) { * return typeof value == 'number'; * }); * // => { 'name': 'moe' } */ function omit(object, callback, thisArg) { var indexOf = getIndexOf(), isFunc = typeof callback == 'function', result = {}; if (isFunc) { callback = lodash.createCallback(callback, thisArg); } else { var props = concat.apply(arrayRef, nativeSlice.call(arguments, 1)); } forIn(object, function(value, key, object) { if (isFunc ? !callback(value, key, object) : indexOf(props, key) < 0 ) { result[key] = value; } }); return result; } /** * Creates a two dimensional array of the given object's key-value pairs, * i.e. `[[key1, value1], [key2, value2]]`. * * @static * @memberOf _ * @category Objects * @param {Object} object The object to inspect. * @returns {Array} Returns new array of key-value pairs. * @example * * _.pairs({ 'moe': 30, 'larry': 40 }); * // => [['moe', 30], ['larry', 40]] (order is not guaranteed) */ function pairs(object) { var index = -1, props = keys(object), length = props.length, result = Array(length); while (++index < length) { var key = props[index]; result[index] = [key, object[key]]; } return result; } /** * Creates a shallow clone of `object` composed of the specified properties. * Property names may be specified as individual arguments or as arrays of property * names. If `callback` is passed, it will be executed for each property in the * `object`, picking the properties `callback` returns truthy for. The `callback` * is bound to `thisArg` and invoked with three arguments; (value, key, object). * * @static * @memberOf _ * @category Objects * @param {Object} object The source object. * @param {Array|Function|String} callback|[prop1, prop2, ...] The function called * per iteration or properties to pick, either as individual arguments or arrays. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns an object composed of the picked properties. * @example * * _.pick({ 'name': 'moe', '_userid': 'moe1' }, 'name'); * // => { 'name': 'moe' } * * _.pick({ 'name': 'moe', '_userid': 'moe1' }, function(value, key) { * return key.charAt(0) != '_'; * }); * // => { 'name': 'moe' } */ function pick(object, callback, thisArg) { var result = {}; if (typeof callback != 'function') { var index = -1, props = concat.apply(arrayRef, nativeSlice.call(arguments, 1)), length = isObject(object) ? props.length : 0; while (++index < length) { var key = props[index]; if (key in object) { result[key] = object[key]; } } } else { callback = lodash.createCallback(callback, thisArg); forIn(object, function(value, key, object) { if (callback(value, key, object)) { result[key] = value; } }); } return result; } /** * An alternative to `_.reduce`, this method transforms an `object` to a new * `accumulator` object which is the result of running each of its elements * through the `callback`, with each `callback` execution potentially mutating * the `accumulator` object. The `callback` is bound to `thisArg` and invoked * with four arguments; (accumulator, value, key, object). Callbacks may exit * iteration early by explicitly returning `false`. * * @static * @memberOf _ * @category Objects * @param {Array|Object} collection The collection to iterate over. * @param {Function} [callback=identity] The function called per iteration. * @param {Mixed} [accumulator] The custom accumulator value. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the accumulated value. * @example * * var squares = _.transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(result, num) { * num *= num; * if (num % 2) { * return result.push(num) < 3; * } * }); * // => [1, 9, 25] * * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) { * result[key] = num * 3; * }); * // => { 'a': 3, 'b': 6, 'c': 9 } */ function transform(object, callback, accumulator, thisArg) { var isArr = isArray(object); callback = lodash.createCallback(callback, thisArg, 4); if (accumulator == null) { if (isArr) { accumulator = []; } else { var ctor = object && object.constructor, proto = ctor && ctor.prototype; accumulator = createObject(proto); } } (isArr ? forEach : forOwn)(object, function(value, index, object) { return callback(accumulator, value, index, object); }); return accumulator; } /** * Creates an array composed of the own enumerable property values of `object`. * * @static * @memberOf _ * @category Objects * @param {Object} object The object to inspect. * @returns {Array} Returns a new array of property values. * @example * * _.values({ 'one': 1, 'two': 2, 'three': 3 }); * // => [1, 2, 3] (order is not guaranteed) */ function values(object) { var index = -1, props = keys(object), length = props.length, result = Array(length); while (++index < length) { result[index] = object[props[index]]; } return result; } /*--------------------------------------------------------------------------*/ /** * Creates an array of elements from the specified indexes, or keys, of the * `collection`. Indexes may be specified as individual arguments or as arrays * of indexes. * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Array|Number|String} [index1, index2, ...] The indexes of * `collection` to retrieve, either as individual arguments or arrays. * @returns {Array} Returns a new array of elements corresponding to the * provided indexes. * @example * * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]); * // => ['a', 'c', 'e'] * * _.at(['moe', 'larry', 'curly'], 0, 2); * // => ['moe', 'curly'] */ function at(collection) { var index = -1, props = concat.apply(arrayRef, nativeSlice.call(arguments, 1)), length = props.length, result = Array(length); while(++index < length) { result[index] = collection[props[index]]; } return result; } /** * Checks if a given `target` element is present in a `collection` using strict * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used * as the offset from the end of the collection. * * @static * @memberOf _ * @alias include * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Mixed} target The value to check for. * @param {Number} [fromIndex=0] The index to search from. * @returns {Boolean} Returns `true` if the `target` element is found, else `false`. * @example * * _.contains([1, 2, 3], 1); * // => true * * _.contains([1, 2, 3], 1, 2); * // => false * * _.contains({ 'name': 'moe', 'age': 40 }, 'moe'); * // => true * * _.contains('curly', 'ur'); * // => true */ function contains(collection, target, fromIndex) { var index = -1, indexOf = getIndexOf(), length = collection ? collection.length : 0, result = false; fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0; if (length && typeof length == 'number') { result = (isString(collection) ? collection.indexOf(target, fromIndex) : indexOf(collection, target, fromIndex) ) > -1; } else { forOwn(collection, function(value) { if (++index >= fromIndex) { return !(result = value === target); } }); } return result; } /** * Creates an object composed of keys returned from running each element of the * `collection` through the given `callback`. The corresponding value of each key * is the number of times the key was returned by the `callback`. The `callback` * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns the composed aggregate object. * @example * * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); }); * // => { '4': 1, '6': 2 } * * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math); * // => { '4': 1, '6': 2 } * * _.countBy(['one', 'two', 'three'], 'length'); * // => { '3': 2, '5': 1 } */ function countBy(collection, callback, thisArg) { var result = {}; callback = lodash.createCallback(callback, thisArg); forEach(collection, function(value, key, collection) { key = String(callback(value, key, collection)); (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1); }); return result; } /** * Checks if the `callback` returns a truthy value for **all** elements of a * `collection`. The `callback` is bound to `thisArg` and invoked with three * arguments; (value, index|key, collection). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @alias all * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Boolean} Returns `true` if all elements pass the callback check, * else `false`. * @example * * _.every([true, 1, null, 'yes'], Boolean); * // => false * * var stooges = [ * { 'name': 'moe', 'age': 40 }, * { 'name': 'larry', 'age': 50 } * ]; * * // using "_.pluck" callback shorthand * _.every(stooges, 'age'); * // => true * * // using "_.where" callback shorthand * _.every(stooges, { 'age': 50 }); * // => false */ function every(collection, callback, thisArg) { var result = true; callback = lodash.createCallback(callback, thisArg); var index = -1, length = collection ? collection.length : 0; if (typeof length == 'number') { while (++index < length) { if (!(result = !!callback(collection[index], index, collection))) { break; } } } else { forOwn(collection, function(value, index, collection) { return (result = !!callback(value, index, collection)); }); } return result; } /** * Examines each element in a `collection`, returning an array of all elements * the `callback` returns truthy for. The `callback` is bound to `thisArg` and * invoked with three arguments; (value, index|key, collection). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @alias select * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a new array of elements that passed the callback check. * @example * * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); * // => [2, 4, 6] * * var food = [ * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } * ]; * * // using "_.pluck" callback shorthand * _.filter(food, 'organic'); * // => [{ 'name': 'carrot', 'organic': true, 'type': 'vegetable' }] * * // using "_.where" callback shorthand * _.filter(food, { 'type': 'fruit' }); * // => [{ 'name': 'apple', 'organic': false, 'type': 'fruit' }] */ function filter(collection, callback, thisArg) { var result = []; callback = lodash.createCallback(callback, thisArg); var index = -1, length = collection ? collection.length : 0; if (typeof length == 'number') { while (++index < length) { var value = collection[index]; if (callback(value, index, collection)) { result.push(value); } } } else { forOwn(collection, function(value, index, collection) { if (callback(value, index, collection)) { result.push(value); } }); } return result; } /** * Examines each element in a `collection`, returning the first that the `callback` * returns truthy for. The `callback` is bound to `thisArg` and invoked with three * arguments; (value, index|key, collection). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @alias detect, findWhere * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the found element, else `undefined`. * @example * * _.find([1, 2, 3, 4], function(num) { * return num % 2 == 0; * }); * // => 2 * * var food = [ * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, * { 'name': 'banana', 'organic': true, 'type': 'fruit' }, * { 'name': 'beet', 'organic': false, 'type': 'vegetable' } * ]; * * // using "_.where" callback shorthand * _.find(food, { 'type': 'vegetable' }); * // => { 'name': 'beet', 'organic': false, 'type': 'vegetable' } * * // using "_.pluck" callback shorthand * _.find(food, 'organic'); * // => { 'name': 'banana', 'organic': true, 'type': 'fruit' } */ function find(collection, callback, thisArg) { callback = lodash.createCallback(callback, thisArg); var index = -1, length = collection ? collection.length : 0; if (typeof length == 'number') { while (++index < length) { var value = collection[index]; if (callback(value, index, collection)) { return value; } } } else { var result; forOwn(collection, function(value, index, collection) { if (callback(value, index, collection)) { result = value; return false; } }); return result; } } /** * Iterates over a `collection`, executing the `callback` for each element in * the `collection`. The `callback` is bound to `thisArg` and invoked with three * arguments; (value, index|key, collection). Callbacks may exit iteration early * by explicitly returning `false`. * * @static * @memberOf _ * @alias each * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} [callback=identity] The function called per iteration. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Array|Object|String} Returns `collection`. * @example * * _([1, 2, 3]).forEach(alert).join(','); * // => alerts each number and returns '1,2,3' * * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert); * // => alerts each number value (order is not guaranteed) */ function forEach(collection, callback, thisArg) { var index = -1, length = collection ? collection.length : 0; callback = callback && typeof thisArg == 'undefined' ? callback : lodash.createCallback(callback, thisArg); if (typeof length == 'number') { while (++index < length) { if (callback(collection[index], index, collection) === false) { break; } } } else { forOwn(collection, callback); } return collection; } /** * Creates an object composed of keys returned from running each element of the * `collection` through the `callback`. The corresponding value of each key is * an array of elements passed to `callback` that returned the key. The `callback` * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false` * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns the composed aggregate object. * @example * * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); }); * // => { '4': [4.2], '6': [6.1, 6.4] } * * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math); * // => { '4': [4.2], '6': [6.1, 6.4] } * * // using "_.pluck" callback shorthand * _.groupBy(['one', 'two', 'three'], 'length'); * // => { '3': ['one', 'two'], '5': ['three'] } */ function groupBy(collection, callback, thisArg) { var result = {}; callback = lodash.createCallback(callback, thisArg); forEach(collection, function(value, key, collection) { key = String(callback(value, key, collection)); (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value); }); return result; } /** * Invokes the method named by `methodName` on each element in the `collection`, * returning an array of the results of each invoked method. Additional arguments * will be passed to each invoked method. If `methodName` is a function, it will * be invoked for, and `this` bound to, each element in the `collection`. * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|String} methodName The name of the method to invoke or * the function invoked per iteration. * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. * @returns {Array} Returns a new array of the results of each invoked method. * @example * * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); * // => [[1, 5, 7], [1, 2, 3]] * * _.invoke([123, 456], String.prototype.split, ''); * // => [['1', '2', '3'], ['4', '5', '6']] */ function invoke(collection, methodName) { var args = nativeSlice.call(arguments, 2), index = -1, isFunc = typeof methodName == 'function', length = collection ? collection.length : 0, result = Array(typeof length == 'number' ? length : 0); forEach(collection, function(value) { result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args); }); return result; } /** * Creates an array of values by running each element in the `collection` * through the `callback`. The `callback` is bound to `thisArg` and invoked with * three arguments; (value, index|key, collection). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @alias collect * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a new array of the results of each `callback` execution. * @example * * _.map([1, 2, 3], function(num) { return num * 3; }); * // => [3, 6, 9] * * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; }); * // => [3, 6, 9] (order is not guaranteed) * * var stooges = [ * { 'name': 'moe', 'age': 40 }, * { 'name': 'larry', 'age': 50 } * ]; * * // using "_.pluck" callback shorthand * _.map(stooges, 'name'); * // => ['moe', 'larry'] */ function map(collection, callback, thisArg) { var index = -1, length = collection ? collection.length : 0; callback = lodash.createCallback(callback, thisArg); if (typeof length == 'number') { var result = Array(length); while (++index < length) { result[index] = callback(collection[index], index, collection); } } else { result = []; forOwn(collection, function(value, key, collection) { result[++index] = callback(value, key, collection); }); } return result; } /** * Retrieves the maximum value of an `array`. If `callback` is passed, * it will be executed for each value in the `array` to generate the * criterion by which the value is ranked. The `callback` is bound to * `thisArg` and invoked with three arguments; (value, index, collection). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the maximum value. * @example * * _.max([4, 2, 8, 6]); * // => 8 * * var stooges = [ * { 'name': 'moe', 'age': 40 }, * { 'name': 'larry', 'age': 50 } * ]; * * _.max(stooges, function(stooge) { return stooge.age; }); * // => { 'name': 'larry', 'age': 50 }; * * // using "_.pluck" callback shorthand * _.max(stooges, 'age'); * // => { 'name': 'larry', 'age': 50 }; */ function max(collection, callback, thisArg) { var computed = -Infinity, result = computed; if (!callback && isArray(collection)) { var index = -1, length = collection.length; while (++index < length) { var value = collection[index]; if (value > result) { result = value; } } } else { callback = (!callback && isString(collection)) ? charAtCallback : lodash.createCallback(callback, thisArg); forEach(collection, function(value, index, collection) { var current = callback(value, index, collection); if (current > computed) { computed = current; result = value; } }); } return result; } /** * Retrieves the minimum value of an `array`. If `callback` is passed, * it will be executed for each value in the `array` to generate the * criterion by which the value is ranked. The `callback` is bound to `thisArg` * and invoked with three arguments; (value, index, collection). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the minimum value. * @example * * _.min([4, 2, 8, 6]); * // => 2 * * var stooges = [ * { 'name': 'moe', 'age': 40 }, * { 'name': 'larry', 'age': 50 } * ]; * * _.min(stooges, function(stooge) { return stooge.age; }); * // => { 'name': 'moe', 'age': 40 }; * * // using "_.pluck" callback shorthand * _.min(stooges, 'age'); * // => { 'name': 'moe', 'age': 40 }; */ function min(collection, callback, thisArg) { var computed = Infinity, result = computed; if (!callback && isArray(collection)) { var index = -1, length = collection.length; while (++index < length) { var value = collection[index]; if (value < result) { result = value; } } } else { callback = (!callback && isString(collection)) ? charAtCallback : lodash.createCallback(callback, thisArg); forEach(collection, function(value, index, collection) { var current = callback(value, index, collection); if (current < computed) { computed = current; result = value; } }); } return result; } /** * Retrieves the value of a specified property from all elements in the `collection`. * * @static * @memberOf _ * @type Function * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {String} property The property to pluck. * @returns {Array} Returns a new array of property values. * @example * * var stooges = [ * { 'name': 'moe', 'age': 40 }, * { 'name': 'larry', 'age': 50 } * ]; * * _.pluck(stooges, 'name'); * // => ['moe', 'larry'] */ function pluck(collection, property) { var index = -1, length = collection ? collection.length : 0; if (typeof length == 'number') { var result = Array(length); while (++index < length) { result[index] = collection[index][property]; } } return result || map(collection, property); } /** * Reduces a `collection` to a value which is the accumulated result of running * each element in the `collection` through the `callback`, where each successive * `callback` execution consumes the return value of the previous execution. * If `accumulator` is not passed, the first element of the `collection` will be * used as the initial `accumulator` value. The `callback` is bound to `thisArg` * and invoked with four arguments; (accumulator, value, index|key, collection). * * @static * @memberOf _ * @alias foldl, inject * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} [callback=identity] The function called per iteration. * @param {Mixed} [accumulator] Initial value of the accumulator. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the accumulated value. * @example * * var sum = _.reduce([1, 2, 3], function(sum, num) { * return sum + num; * }); * // => 6 * * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) { * result[key] = num * 3; * return result; * }, {}); * // => { 'a': 3, 'b': 6, 'c': 9 } */ function reduce(collection, callback, accumulator, thisArg) { if (!collection) return accumulator; var noaccum = arguments.length < 3; callback = lodash.createCallback(callback, thisArg, 4); var index = -1, length = collection.length; if (typeof length == 'number') { if (noaccum) { accumulator = collection[++index]; } while (++index < length) { accumulator = callback(accumulator, collection[index], index, collection); } } else { forOwn(collection, function(value, index, collection) { accumulator = noaccum ? (noaccum = false, value) : callback(accumulator, value, index, collection) }); } return accumulator; } /** * This method is similar to `_.reduce`, except that it iterates over a * `collection` from right to left. * * @static * @memberOf _ * @alias foldr * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} [callback=identity] The function called per iteration. * @param {Mixed} [accumulator] Initial value of the accumulator. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the accumulated value. * @example * * var list = [[0, 1], [2, 3], [4, 5]]; * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []); * // => [4, 5, 2, 3, 0, 1] */ function reduceRight(collection, callback, accumulator, thisArg) { var iterable = collection, length = collection ? collection.length : 0, noaccum = arguments.length < 3; if (typeof length != 'number') { var props = keys(collection); length = props.length; } callback = lodash.createCallback(callback, thisArg, 4); forEach(collection, function(value, index, collection) { index = props ? props[--length] : --length; accumulator = noaccum ? (noaccum = false, iterable[index]) : callback(accumulator, iterable[index], index, collection); }); return accumulator; } /** * The opposite of `_.filter`, this method returns the elements of a * `collection` that `callback` does **not** return truthy for. * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a new array of elements that did **not** pass the * callback check. * @example * * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); * // => [1, 3, 5] * * var food = [ * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } * ]; * * // using "_.pluck" callback shorthand * _.reject(food, 'organic'); * // => [{ 'name': 'apple', 'organic': false, 'type': 'fruit' }] * * // using "_.where" callback shorthand * _.reject(food, { 'type': 'fruit' }); * // => [{ 'name': 'carrot', 'organic': true, 'type': 'vegetable' }] */ function reject(collection, callback, thisArg) { callback = lodash.createCallback(callback, thisArg); return filter(collection, function(value, index, collection) { return !callback(value, index, collection); }); } /** * Creates an array of shuffled `array` values, using a version of the * Fisher-Yates shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to shuffle. * @returns {Array} Returns a new shuffled collection. * @example * * _.shuffle([1, 2, 3, 4, 5, 6]); * // => [4, 1, 6, 3, 5, 2] */ function shuffle(collection) { var index = -1, length = collection ? collection.length : 0, result = Array(typeof length == 'number' ? length : 0); forEach(collection, function(value) { var rand = floor(nativeRandom() * (++index + 1)); result[index] = result[rand]; result[rand] = value; }); return result; } /** * Gets the size of the `collection` by returning `collection.length` for arrays * and array-like objects or the number of own enumerable properties for objects. * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to inspect. * @returns {Number} Returns `collection.length` or number of own enumerable properties. * @example * * _.size([1, 2]); * // => 2 * * _.size({ 'one': 1, 'two': 2, 'three': 3 }); * // => 3 * * _.size('curly'); * // => 5 */ function size(collection) { var length = collection ? collection.length : 0; return typeof length == 'number' ? length : keys(collection).length; } /** * Checks if the `callback` returns a truthy value for **any** element of a * `collection`. The function returns as soon as it finds passing value, and * does not iterate over the entire `collection`. The `callback` is bound to * `thisArg` and invoked with three arguments; (value, index|key, collection). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @alias any * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Boolean} Returns `true` if any element passes the callback check, * else `false`. * @example * * _.some([null, 0, 'yes', false], Boolean); * // => true * * var food = [ * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } * ]; * * // using "_.pluck" callback shorthand * _.some(food, 'organic'); * // => true * * // using "_.where" callback shorthand * _.some(food, { 'type': 'meat' }); * // => false */ function some(collection, callback, thisArg) { var result; callback = lodash.createCallback(callback, thisArg); var index = -1, length = collection ? collection.length : 0; if (typeof length == 'number') { while (++index < length) { if ((result = callback(collection[index], index, collection))) { break; } } } else { forOwn(collection, function(value, index, collection) { return !(result = callback(value, index, collection)); }); } return !!result; } /** * Creates an array of elements, sorted in ascending order by the results of * running each element in the `collection` through the `callback`. This method * performs a stable sort, that is, it will preserve the original sort order of * equal elements. The `callback` is bound to `thisArg` and invoked with three * arguments; (value, index|key, collection). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a new array of sorted elements. * @example * * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); }); * // => [3, 1, 2] * * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math); * // => [3, 1, 2] * * // using "_.pluck" callback shorthand * _.sortBy(['banana', 'strawberry', 'apple'], 'length'); * // => ['apple', 'banana', 'strawberry'] */ function sortBy(collection, callback, thisArg) { var index = -1, length = collection ? collection.length : 0, result = Array(typeof length == 'number' ? length : 0); callback = lodash.createCallback(callback, thisArg); forEach(collection, function(value, key, collection) { var object = result[++index] = getObject(); object.criteria = callback(value, key, collection); object.index = index; object.value = value; }); length = result.length; result.sort(compareAscending); while (length--) { var object = result[length]; result[length] = object.value; releaseObject(object); } return result; } /** * Converts the `collection` to an array. * * @static * @memberOf _ * @category Collections * @param {Array|Object|String} collection The collection to convert. * @returns {Array} Returns the new converted array. * @example * * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4); * // => [2, 3, 4] */ function toArray(collection) { if (collection && typeof collection.length == 'number') { return slice(collection); } return values(collection); } /** * Examines each element in a `collection`, returning an array of all elements * that have the given `properties`. When checking `properties`, this method * performs a deep comparison between values to determine if they are equivalent * to each other. * * @static * @memberOf _ * @type Function * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Object} properties The object of property values to filter by. * @returns {Array} Returns a new array of elements that have the given `properties`. * @example * * var stooges = [ * { 'name': 'moe', 'age': 40 }, * { 'name': 'larry', 'age': 50 } * ]; * * _.where(stooges, { 'age': 40 }); * // => [{ 'name': 'moe', 'age': 40 }] */ var where = filter; /*--------------------------------------------------------------------------*/ /** * Creates an array with all falsey values of `array` removed. The values * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to compact. * @returns {Array} Returns a new filtered array. * @example * * _.compact([0, 1, false, 2, '', 3]); * // => [1, 2, 3] */ function compact(array) { var index = -1, length = array ? array.length : 0, result = []; while (++index < length) { var value = array[index]; if (value) { result.push(value); } } return result; } /** * Creates an array of `array` elements not present in the other arrays * using strict equality for comparisons, i.e. `===`. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to process. * @param {Array} [array1, array2, ...] Arrays to check. * @returns {Array} Returns a new array of `array` elements not present in the * other arrays. * @example * * _.difference([1, 2, 3, 4, 5], [5, 2, 10]); * // => [1, 3, 4] */ function difference(array) { var index = -1, indexOf = getIndexOf(), length = array ? array.length : 0, seen = concat.apply(arrayRef, nativeSlice.call(arguments, 1)), result = []; var isLarge = length >= largeArraySize && indexOf === basicIndexOf; if (isLarge) { var cache = createCache(seen); if (cache) { indexOf = cacheIndexOf; seen = cache; } else { isLarge = false; } } while (++index < length) { var value = array[index]; if (indexOf(seen, value) < 0) { result.push(value); } } if (isLarge) { releaseObject(seen); } return result; } /** * This method is similar to `_.find`, except that it returns the index of * the element that passes the callback check, instead of the element itself. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to search. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the index of the found element, else `-1`. * @example * * _.findIndex(['apple', 'banana', 'beet'], function(food) { * return /^b/.test(food); * }); * // => 1 */ function findIndex(array, callback, thisArg) { var index = -1, length = array ? array.length : 0; callback = lodash.createCallback(callback, thisArg); while (++index < length) { if (callback(array[index], index, array)) { return index; } } return -1; } /** * Gets the first element of the `array`. If a number `n` is passed, the first * `n` elements of the `array` are returned. If a `callback` function is passed, * elements at the beginning of the array are returned as long as the `callback` * returns truthy. The `callback` is bound to `thisArg` and invoked with three * arguments; (value, index, array). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @alias head, take * @category Arrays * @param {Array} array The array to query. * @param {Function|Object|Number|String} [callback|n] The function called * per element or the number of elements to return. If a property name or * object is passed, it will be used to create a "_.pluck" or "_.where" * style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the first element(s) of `array`. * @example * * _.first([1, 2, 3]); * // => 1 * * _.first([1, 2, 3], 2); * // => [1, 2] * * _.first([1, 2, 3], function(num) { * return num < 3; * }); * // => [1, 2] * * var food = [ * { 'name': 'banana', 'organic': true }, * { 'name': 'beet', 'organic': false }, * ]; * * // using "_.pluck" callback shorthand * _.first(food, 'organic'); * // => [{ 'name': 'banana', 'organic': true }] * * var food = [ * { 'name': 'apple', 'type': 'fruit' }, * { 'name': 'banana', 'type': 'fruit' }, * { 'name': 'beet', 'type': 'vegetable' } * ]; * * // using "_.where" callback shorthand * _.first(food, { 'type': 'fruit' }); * // => [{ 'name': 'apple', 'type': 'fruit' }, { 'name': 'banana', 'type': 'fruit' }] */ function first(array, callback, thisArg) { if (array) { var n = 0, length = array.length; if (typeof callback != 'number' && callback != null) { var index = -1; callback = lodash.createCallback(callback, thisArg); while (++index < length && callback(array[index], index, array)) { n++; } } else { n = callback; if (n == null || thisArg) { return array[0]; } } return slice(array, 0, nativeMin(nativeMax(0, n), length)); } } /** * Flattens a nested array (the nesting can be to any depth). If `isShallow` * is truthy, `array` will only be flattened a single level. If `callback` * is passed, each element of `array` is passed through a `callback` before * flattening. The `callback` is bound to `thisArg` and invoked with three * arguments; (value, index, array). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to flatten. * @param {Boolean} [isShallow=false] A flag to indicate only flattening a single level. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a new flattened array. * @example * * _.flatten([1, [2], [3, [[4]]]]); * // => [1, 2, 3, 4]; * * _.flatten([1, [2], [3, [[4]]]], true); * // => [1, 2, 3, [[4]]]; * * var stooges = [ * { 'name': 'curly', 'quotes': ['Oh, a wise guy, eh?', 'Poifect!'] }, * { 'name': 'moe', 'quotes': ['Spread out!', 'You knucklehead!'] } * ]; * * // using "_.pluck" callback shorthand * _.flatten(stooges, 'quotes'); * // => ['Oh, a wise guy, eh?', 'Poifect!', 'Spread out!', 'You knucklehead!'] */ var flatten = overloadWrapper(function flatten(array, isShallow, callback) { var index = -1, length = array ? array.length : 0, result = []; while (++index < length) { var value = array[index]; if (callback) { value = callback(value, index, array); } // recursively flatten arrays (susceptible to call stack limits) if (isArray(value)) { push.apply(result, isShallow ? value : flatten(value)); } else { result.push(value); } } return result; }); /** * Gets the index at which the first occurrence of `value` is found using * strict equality for comparisons, i.e. `===`. If the `array` is already * sorted, passing `true` for `fromIndex` will run a faster binary search. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to search. * @param {Mixed} value The value to search for. * @param {Boolean|Number} [fromIndex=0] The index to search from or `true` to * perform a binary search on a sorted `array`. * @returns {Number} Returns the index of the matched value or `-1`. * @example * * _.indexOf([1, 2, 3, 1, 2, 3], 2); * // => 1 * * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3); * // => 4 * * _.indexOf([1, 1, 2, 2, 3, 3], 2, true); * // => 2 */ function indexOf(array, value, fromIndex) { if (typeof fromIndex == 'number') { var length = array ? array.length : 0; fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0); } else if (fromIndex) { var index = sortedIndex(array, value); return array[index] === value ? index : -1; } return array ? basicIndexOf(array, value, fromIndex) : -1; } /** * Gets all but the last element of `array`. If a number `n` is passed, the * last `n` elements are excluded from the result. If a `callback` function * is passed, elements at the end of the array are excluded from the result * as long as the `callback` returns truthy. The `callback` is bound to * `thisArg` and invoked with three arguments; (value, index, array). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to query. * @param {Function|Object|Number|String} [callback|n=1] The function called * per element or the number of elements to exclude. If a property name or * object is passed, it will be used to create a "_.pluck" or "_.where" * style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a slice of `array`. * @example * * _.initial([1, 2, 3]); * // => [1, 2] * * _.initial([1, 2, 3], 2); * // => [1] * * _.initial([1, 2, 3], function(num) { * return num > 1; * }); * // => [1] * * var food = [ * { 'name': 'beet', 'organic': false }, * { 'name': 'carrot', 'organic': true } * ]; * * // using "_.pluck" callback shorthand * _.initial(food, 'organic'); * // => [{ 'name': 'beet', 'organic': false }] * * var food = [ * { 'name': 'banana', 'type': 'fruit' }, * { 'name': 'beet', 'type': 'vegetable' }, * { 'name': 'carrot', 'type': 'vegetable' } * ]; * * // using "_.where" callback shorthand * _.initial(food, { 'type': 'vegetable' }); * // => [{ 'name': 'banana', 'type': 'fruit' }] */ function initial(array, callback, thisArg) { if (!array) { return []; } var n = 0, length = array.length; if (typeof callback != 'number' && callback != null) { var index = length; callback = lodash.createCallback(callback, thisArg); while (index-- && callback(array[index], index, array)) { n++; } } else { n = (callback == null || thisArg) ? 1 : callback || n; } return slice(array, 0, nativeMin(nativeMax(0, length - n), length)); } /** * Computes the intersection of all the passed-in arrays using strict equality * for comparisons, i.e. `===`. * * @static * @memberOf _ * @category Arrays * @param {Array} [array1, array2, ...] Arrays to process. * @returns {Array} Returns a new array of unique elements that are present * in **all** of the arrays. * @example * * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); * // => [1, 2] */ function intersection(array) { var args = arguments, argsLength = args.length, argsIndex = -1, caches = getArray(), index = -1, indexOf = getIndexOf(), length = array ? array.length : 0, result = [], seen = getArray(); while (++argsIndex < argsLength) { var value = args[argsIndex]; caches[argsIndex] = indexOf === basicIndexOf && (value ? value.length : 0) >= largeArraySize && createCache(argsIndex ? args[argsIndex] : seen); } outer: while (++index < length) { var cache = caches[0]; value = array[index]; if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) { argsIndex = argsLength; (cache || seen).push(value); while (--argsIndex) { cache = caches[argsIndex]; if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) { continue outer; } } result.push(value); } } while (argsLength--) { cache = caches[argsLength]; if (cache) { releaseObject(cache); } } releaseArray(caches); releaseArray(seen); return result; } /** * Gets the last element of the `array`. If a number `n` is passed, the * last `n` elements of the `array` are returned. If a `callback` function * is passed, elements at the end of the array are returned as long as the * `callback` returns truthy. The `callback` is bound to `thisArg` and * invoked with three arguments;(value, index, array). * * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to query. * @param {Function|Object|Number|String} [callback|n] The function called * per element or the number of elements to return. If a property name or * object is passed, it will be used to create a "_.pluck" or "_.where" * style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Mixed} Returns the last element(s) of `array`. * @example * * _.last([1, 2, 3]); * // => 3 * * _.last([1, 2, 3], 2); * // => [2, 3] * * _.last([1, 2, 3], function(num) { * return num > 1; * }); * // => [2, 3] * * var food = [ * { 'name': 'beet', 'organic': false }, * { 'name': 'carrot', 'organic': true } * ]; * * // using "_.pluck" callback shorthand * _.last(food, 'organic'); * // => [{ 'name': 'carrot', 'organic': true }] * * var food = [ * { 'name': 'banana', 'type': 'fruit' }, * { 'name': 'beet', 'type': 'vegetable' }, * { 'name': 'carrot', 'type': 'vegetable' } * ]; * * // using "_.where" callback shorthand * _.last(food, { 'type': 'vegetable' }); * // => [{ 'name': 'beet', 'type': 'vegetable' }, { 'name': 'carrot', 'type': 'vegetable' }] */ function last(array, callback, thisArg) { if (array) { var n = 0, length = array.length; if (typeof callback != 'number' && callback != null) { var index = length; callback = lodash.createCallback(callback, thisArg); while (index-- && callback(array[index], index, array)) { n++; } } else { n = callback; if (n == null || thisArg) { return array[length - 1]; } } return slice(array, nativeMax(0, length - n)); } } /** * Gets the index at which the last occurrence of `value` is found using strict * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used * as the offset from the end of the collection. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to search. * @param {Mixed} value The value to search for. * @param {Number} [fromIndex=array.length-1] The index to search from. * @returns {Number} Returns the index of the matched value or `-1`. * @example * * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); * // => 4 * * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3); * // => 1 */ function lastIndexOf(array, value, fromIndex) { var index = array ? array.length : 0; if (typeof fromIndex == 'number') { index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1; } while (index--) { if (array[index] === value) { return index; } } return -1; } /** * Creates an array of numbers (positive and/or negative) progressing from * `start` up to but not including `end`. * * @static * @memberOf _ * @category Arrays * @param {Number} [start=0] The start of the range. * @param {Number} end The end of the range. * @param {Number} [step=1] The value to increment or decrement by. * @returns {Array} Returns a new range array. * @example * * _.range(10); * // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] * * _.range(1, 11); * // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] * * _.range(0, 30, 5); * // => [0, 5, 10, 15, 20, 25] * * _.range(0, -10, -1); * // => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] * * _.range(0); * // => [] */ function range(start, end, step) { start = +start || 0; step = +step || 1; if (end == null) { end = start; start = 0; } // use `Array(length)` so V8 will avoid the slower "dictionary" mode // http://youtu.be/XAqIpGU8ZZk#t=17m25s var index = -1, length = nativeMax(0, ceil((end - start) / step)), result = Array(length); while (++index < length) { result[index] = start; start += step; } return result; } /** * The opposite of `_.initial`, this method gets all but the first value of * `array`. If a number `n` is passed, the first `n` values are excluded from * the result. If a `callback` function is passed, elements at the beginning * of the array are excluded from the result as long as the `callback` returns * truthy. The `callback` is bound to `thisArg` and invoked with three * arguments; (value, index, array). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @alias drop, tail * @category Arrays * @param {Array} array The array to query. * @param {Function|Object|Number|String} [callback|n=1] The function called * per element or the number of elements to exclude. If a property name or * object is passed, it will be used to create a "_.pluck" or "_.where" * style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a slice of `array`. * @example * * _.rest([1, 2, 3]); * // => [2, 3] * * _.rest([1, 2, 3], 2); * // => [3] * * _.rest([1, 2, 3], function(num) { * return num < 3; * }); * // => [3] * * var food = [ * { 'name': 'banana', 'organic': true }, * { 'name': 'beet', 'organic': false }, * ]; * * // using "_.pluck" callback shorthand * _.rest(food, 'organic'); * // => [{ 'name': 'beet', 'organic': false }] * * var food = [ * { 'name': 'apple', 'type': 'fruit' }, * { 'name': 'banana', 'type': 'fruit' }, * { 'name': 'beet', 'type': 'vegetable' } * ]; * * // using "_.where" callback shorthand * _.rest(food, { 'type': 'fruit' }); * // => [{ 'name': 'beet', 'type': 'vegetable' }] */ function rest(array, callback, thisArg) { if (typeof callback != 'number' && callback != null) { var n = 0, index = -1, length = array ? array.length : 0; callback = lodash.createCallback(callback, thisArg); while (++index < length && callback(array[index], index, array)) { n++; } } else { n = (callback == null || thisArg) ? 1 : nativeMax(0, callback); } return slice(array, n); } /** * Uses a binary search to determine the smallest index at which the `value` * should be inserted into `array` in order to maintain the sort order of the * sorted `array`. If `callback` is passed, it will be executed for `value` and * each element in `array` to compute their sort ranking. The `callback` is * bound to `thisArg` and invoked with one argument; (value). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to inspect. * @param {Mixed} value The value to evaluate. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Number} Returns the index at which the value should be inserted * into `array`. * @example * * _.sortedIndex([20, 30, 50], 40); * // => 2 * * // using "_.pluck" callback shorthand * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x'); * // => 2 * * var dict = { * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 } * }; * * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { * return dict.wordToNumber[word]; * }); * // => 2 * * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { * return this.wordToNumber[word]; * }, dict); * // => 2 */ function sortedIndex(array, value, callback, thisArg) { var low = 0, high = array ? array.length : low; // explicitly reference `identity` for better inlining in Firefox callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity; value = callback(value); while (low < high) { var mid = (low + high) >>> 1; (callback(array[mid]) < value) ? low = mid + 1 : high = mid; } return low; } /** * Computes the union of the passed-in arrays using strict equality for * comparisons, i.e. `===`. * * @static * @memberOf _ * @category Arrays * @param {Array} [array1, array2, ...] Arrays to process. * @returns {Array} Returns a new array of unique values, in order, that are * present in one or more of the arrays. * @example * * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); * // => [1, 2, 3, 101, 10] */ function union(array) { if (!isArray(array)) { arguments[0] = array ? nativeSlice.call(array) : arrayRef; } return uniq(concat.apply(arrayRef, arguments)); } /** * Creates a duplicate-value-free version of the `array` using strict equality * for comparisons, i.e. `===`. If the `array` is already sorted, passing `true` * for `isSorted` will run a faster algorithm. If `callback` is passed, each * element of `array` is passed through the `callback` before uniqueness is computed. * The `callback` is bound to `thisArg` and invoked with three arguments; (value, index, array). * * If a property name is passed for `callback`, the created "_.pluck" style * callback will return the property value of the given element. * * If an object is passed for `callback`, the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ * @alias unique * @category Arrays * @param {Array} array The array to process. * @param {Boolean} [isSorted=false] A flag to indicate that the `array` is already sorted. * @param {Function|Object|String} [callback=identity] The function called per * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a duplicate-value-free array. * @example * * _.uniq([1, 2, 1, 3, 1]); * // => [1, 2, 3] * * _.uniq([1, 1, 2, 2, 3], true); * // => [1, 2, 3] * * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); }); * // => ['A', 'b', 'C'] * * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math); * // => [1, 2.5, 3] * * // using "_.pluck" callback shorthand * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); * // => [{ 'x': 1 }, { 'x': 2 }] */ var uniq = overloadWrapper(function(array, isSorted, callback) { var index = -1, indexOf = getIndexOf(), length = array ? array.length : 0, result = []; var isLarge = !isSorted && length >= largeArraySize && indexOf === basicIndexOf, seen = (callback || isLarge) ? getArray() : result; if (isLarge) { var cache = createCache(seen); if (cache) { indexOf = cacheIndexOf; seen = cache; } else { isLarge = false; seen = callback ? seen : (releaseArray(seen), result); } } while (++index < length) { var value = array[index], computed = callback ? callback(value, index, array) : value; if (isSorted ? !index || seen[seen.length - 1] !== computed : indexOf(seen, computed) < 0 ) { if (callback || isLarge) { seen.push(computed); } result.push(value); } } if (isLarge) { releaseArray(seen.array); releaseObject(seen); } else if (callback) { releaseArray(seen); } return result; }); /** * The inverse of `_.zip`, this method splits groups of elements into arrays * composed of elements from each group at their corresponding indexes. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to process. * @returns {Array} Returns a new array of the composed arrays. * @example * * _.unzip([['moe', 30, true], ['larry', 40, false]]); * // => [['moe', 'larry'], [30, 40], [true, false]]; */ function unzip(array) { var index = -1, length = array ? max(pluck(array, 'length')) : 0, result = Array(length < 0 ? 0 : length); while (++index < length) { result[index] = pluck(array, index); } return result; } /** * Creates an array with all occurrences of the passed values removed using * strict equality for comparisons, i.e. `===`. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to filter. * @param {Mixed} [value1, value2, ...] Values to remove. * @returns {Array} Returns a new filtered array. * @example * * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); * // => [2, 3, 4] */ function without(array) { return difference(array, nativeSlice.call(arguments, 1)); } /** * Groups the elements of each array at their corresponding indexes. Useful for * separate data sources that are coordinated through matching array indexes. * For a matrix of nested arrays, `_.zip.apply(...)` can transpose the matrix * in a similar fashion. * * @static * @memberOf _ * @category Arrays * @param {Array} [array1, array2, ...] Arrays to process. * @returns {Array} Returns a new array of grouped elements. * @example * * _.zip(['moe', 'larry'], [30, 40], [true, false]); * // => [['moe', 30, true], ['larry', 40, false]] */ function zip(array) { return array ? unzip(arguments) : []; } /** * Creates an object composed from arrays of `keys` and `values`. Pass either * a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`, or * two arrays, one of `keys` and one of corresponding `values`. * * @static * @memberOf _ * @alias object * @category Arrays * @param {Array} keys The array of keys. * @param {Array} [values=[]] The array of values. * @returns {Object} Returns an object composed of the given keys and * corresponding values. * @example * * _.zipObject(['moe', 'larry'], [30, 40]); * // => { 'moe': 30, 'larry': 40 } */ function zipObject(keys, values) { var index = -1, length = keys ? keys.length : 0, result = {}; while (++index < length) { var key = keys[index]; if (values) { result[key] = values[index]; } else { result[key[0]] = key[1]; } } return result; } /*--------------------------------------------------------------------------*/ /** * If `n` is greater than `0`, a function is created that is restricted to * executing `func`, with the `this` binding and arguments of the created * function, only after it is called `n` times. If `n` is less than `1`, * `func` is executed immediately, without a `this` binding or additional * arguments, and its result is returned. * * @static * @memberOf _ * @category Functions * @param {Number} n The number of times the function must be called before * it is executed. * @param {Function} func The function to restrict. * @returns {Function} Returns the new restricted function. * @example * * var renderNotes = _.after(notes.length, render); * _.forEach(notes, function(note) { * note.asyncSave({ 'success': renderNotes }); * }); * // `renderNotes` is run once, after all notes have saved */ function after(n, func) { if (n < 1) { return func(); } return function() { if (--n < 1) { return func.apply(this, arguments); } }; } /** * Creates a function that, when called, invokes `func` with the `this` * binding of `thisArg` and prepends any additional `bind` arguments to those * passed to the bound function. * * @static * @memberOf _ * @category Functions * @param {Function} func The function to bind. * @param {Mixed} [thisArg] The `this` binding of `func`. * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. * @returns {Function} Returns the new bound function. * @example * * var func = function(greeting) { * return greeting + ' ' + this.name; * }; * * func = _.bind(func, { 'name': 'moe' }, 'hi'); * func(); * // => 'hi moe' */ function bind(func, thisArg) { // use `Function#bind` if it exists and is fast // (in V8 `Function#bind` is slower except when partially applied) return support.fastBind || (nativeBind && arguments.length > 2) ? nativeBind.call.apply(nativeBind, arguments) : createBound(func, thisArg, nativeSlice.call(arguments, 2)); } /** * Binds methods on `object` to `object`, overwriting the existing method. * Method names may be specified as individual arguments or as arrays of method * names. If no method names are provided, all the function properties of `object` * will be bound. * * @static * @memberOf _ * @category Functions * @param {Object} object The object to bind and assign the bound methods to. * @param {String} [methodName1, methodName2, ...] Method names on the object to bind. * @returns {Object} Returns `object`. * @example * * var view = { * 'label': 'docs', * 'onClick': function() { alert('clicked ' + this.label); } * }; * * _.bindAll(view); * jQuery('#docs').on('click', view.onClick); * // => alerts 'clicked docs', when the button is clicked */ function bindAll(object) { var funcs = arguments.length > 1 ? concat.apply(arrayRef, nativeSlice.call(arguments, 1)) : functions(object), index = -1, length = funcs.length; while (++index < length) { var key = funcs[index]; object[key] = bind(object[key], object); } return object; } /** * Creates a function that, when called, invokes the method at `object[key]` * and prepends any additional `bindKey` arguments to those passed to the bound * function. This method differs from `_.bind` by allowing bound functions to * reference methods that will be redefined or don't yet exist. * See http://michaux.ca/articles/lazy-function-definition-pattern. * * @static * @memberOf _ * @category Functions * @param {Object} object The object the method belongs to. * @param {String} key The key of the method. * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. * @returns {Function} Returns the new bound function. * @example * * var object = { * 'name': 'moe', * 'greet': function(greeting) { * return greeting + ' ' + this.name; * } * }; * * var func = _.bindKey(object, 'greet', 'hi'); * func(); * // => 'hi moe' * * object.greet = function(greeting) { * return greeting + ', ' + this.name + '!'; * }; * * func(); * // => 'hi, moe!' */ function bindKey(object, key) { return createBound(object, key, nativeSlice.call(arguments, 2), indicatorObject); } /** * Creates a function that is the composition of the passed functions, * where each function consumes the return value of the function that follows. * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. * Each function is executed with the `this` binding of the composed function. * * @static * @memberOf _ * @category Functions * @param {Function} [func1, func2, ...] Functions to compose. * @returns {Function} Returns the new composed function. * @example * * var greet = function(name) { return 'hi ' + name; }; * var exclaim = function(statement) { return statement + '!'; }; * var welcome = _.compose(exclaim, greet); * welcome('moe'); * // => 'hi moe!' */ function compose() { var funcs = arguments; return function() { var args = arguments, length = funcs.length; while (length--) { args = [funcs[length].apply(this, args)]; } return args[0]; }; } /** * Produces a callback bound to an optional `thisArg`. If `func` is a property * name, the created callback will return the property value for a given element. * If `func` is an object, the created callback will return `true` for elements * that contain the equivalent object properties, otherwise it will return `false`. * * Note: All Lo-Dash methods, that accept a `callback` argument, use `_.createCallback`. * * @static * @memberOf _ * @category Functions * @param {Mixed} [func=identity] The value to convert to a callback. * @param {Mixed} [thisArg] The `this` binding of the created callback. * @param {Number} [argCount=3] The number of arguments the callback accepts. * @returns {Function} Returns a callback function. * @example * * var stooges = [ * { 'name': 'moe', 'age': 40 }, * { 'name': 'larry', 'age': 50 } * ]; * * // wrap to create custom callback shorthands * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) { * var match = /^(.+?)__([gl]t)(.+)$/.exec(callback); * return !match ? func(callback, thisArg) : function(object) { * return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3]; * }; * }); * * _.filter(stooges, 'age__gt45'); * // => [{ 'name': 'larry', 'age': 50 }] * * // create mixins with support for "_.pluck" and "_.where" callback shorthands * _.mixin({ * 'toLookup': function(collection, callback, thisArg) { * callback = _.createCallback(callback, thisArg); * return _.reduce(collection, function(result, value, index, collection) { * return (result[callback(value, index, collection)] = value, result); * }, {}); * } * }); * * _.toLookup(stooges, 'name'); * // => { 'moe': { 'name': 'moe', 'age': 40 }, 'larry': { 'name': 'larry', 'age': 50 } } */ function createCallback(func, thisArg, argCount) { if (func == null) { return identity; } var type = typeof func; if (type != 'function') { if (type != 'object') { return function(object) { return object[func]; }; } var props = keys(func); return function(object) { var length = props.length, result = false; while (length--) { if (!(result = isEqual(object[props[length]], func[props[length]], indicatorObject))) { break; } } return result; }; } if (typeof thisArg == 'undefined' || (reThis && !reThis.test(fnToString.call(func)))) { return func; } if (argCount === 1) { return function(value) { return func.call(thisArg, value); }; } if (argCount === 2) { return function(a, b) { return func.call(thisArg, a, b); }; } if (argCount === 4) { return function(accumulator, value, index, collection) { return func.call(thisArg, accumulator, value, index, collection); }; } return function(value, index, collection) { return func.call(thisArg, value, index, collection); }; } /** * Creates a function that will delay the execution of `func` until after * `wait` milliseconds have elapsed since the last time it was invoked. Pass * an `options` object to indicate that `func` should be invoked on the leading * and/or trailing edge of the `wait` timeout. Subsequent calls to the debounced * function will return the result of the last `func` call. * * Note: If `leading` and `trailing` options are `true`, `func` will be called * on the trailing edge of the timeout only if the the debounced function is * invoked more than once during the `wait` timeout. * * @static * @memberOf _ * @category Functions * @param {Function} func The function to debounce. * @param {Number} wait The number of milliseconds to delay. * @param {Object} options The options object. * [leading=false] A boolean to specify execution on the leading edge of the timeout. * [maxWait] The maximum time `func` is allowed to be delayed before it's called. * [trailing=true] A boolean to specify execution on the trailing edge of the timeout. * @returns {Function} Returns the new debounced function. * @example * * var lazyLayout = _.debounce(calculateLayout, 300); * jQuery(window).on('resize', lazyLayout); * * jQuery('#postbox').on('click', _.debounce(sendMail, 200, { * 'leading': true, * 'trailing': false * }); */ function debounce(func, wait, options) { var args, result, thisArg, callCount = 0, lastCalled = 0, maxWait = false, maxTimeoutId = null, timeoutId = null, trailing = true; function clear() { clearTimeout(maxTimeoutId); clearTimeout(timeoutId); callCount = 0; maxTimeoutId = timeoutId = null; } function delayed() { var isCalled = trailing && (!leading || callCount > 1); clear(); if (isCalled) { if (maxWait !== false) { lastCalled = new Date; } result = func.apply(thisArg, args); } } function maxDelayed() { clear(); if (trailing || (maxWait !== wait)) { lastCalled = new Date; result = func.apply(thisArg, args); } } wait = nativeMax(0, wait || 0); if (options === true) { var leading = true; trailing = false; } else if (isObject(options)) { leading = options.leading; maxWait = 'maxWait' in options && nativeMax(wait, options.maxWait || 0); trailing = 'trailing' in options ? options.trailing : trailing; } return function() { args = arguments; thisArg = this; callCount++; // avoid issues with Titanium and `undefined` timeout ids // https://github.com/appcelerator/titanium_mobile/blob/3_1_0_GA/android/titanium/src/java/ti/modules/titanium/TitaniumModule.java#L185-L192 clearTimeout(timeoutId); if (maxWait === false) { if (leading && callCount < 2) { result = func.apply(thisArg, args); } } else { var now = new Date; if (!maxTimeoutId && !leading) { lastCalled = now; } var remaining = maxWait - (now - lastCalled); if (remaining <= 0) { clearTimeout(maxTimeoutId); maxTimeoutId = null; lastCalled = now; result = func.apply(thisArg, args); } else if (!maxTimeoutId) { maxTimeoutId = setTimeout(maxDelayed, remaining); } } if (wait !== maxWait) { timeoutId = setTimeout(delayed, wait); } return result; }; } /** * Defers executing the `func` function until the current call stack has cleared. * Additional arguments will be passed to `func` when it is invoked. * * @static * @memberOf _ * @category Functions * @param {Function} func The function to defer. * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. * @returns {Number} Returns the timer id. * @example * * _.defer(function() { alert('deferred'); }); * // returns from the function before `alert` is called */ function defer(func) { var args = nativeSlice.call(arguments, 1); return setTimeout(function() { func.apply(undefined, args); }, 1); } // use `setImmediate` if it's available in Node.js if (isV8 && freeModule && typeof setImmediate == 'function') { defer = bind(setImmediate, context); } /** * Executes the `func` function after `wait` milliseconds. Additional arguments * will be passed to `func` when it is invoked. * * @static * @memberOf _ * @category Functions * @param {Function} func The function to delay. * @param {Number} wait The number of milliseconds to delay execution. * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. * @returns {Number} Returns the timer id. * @example * * var log = _.bind(console.log, console); * _.delay(log, 1000, 'logged later'); * // => 'logged later' (Appears after one second.) */ function delay(func, wait) { var args = nativeSlice.call(arguments, 2); return setTimeout(function() { func.apply(undefined, args); }, wait); } /** * Creates a function that memoizes the result of `func`. If `resolver` is * passed, it will be used to determine the cache key for storing the result * based on the arguments passed to the memoized function. By default, the first * argument passed to the memoized function is used as the cache key. The `func` * is executed with the `this` binding of the memoized function. The result * cache is exposed as the `cache` property on the memoized function. * * @static * @memberOf _ * @category Functions * @param {Function} func The function to have its output memoized. * @param {Function} [resolver] A function used to resolve the cache key. * @returns {Function} Returns the new memoizing function. * @example * * var fibonacci = _.memoize(function(n) { * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); * }); */ function memoize(func, resolver) { function memoized() { var cache = memoized.cache, key = keyPrefix + (resolver ? resolver.apply(this, arguments) : arguments[0]); return hasOwnProperty.call(cache, key) ? cache[key] : (cache[key] = func.apply(this, arguments)); } memoized.cache = {}; return memoized; } /** * Creates a function that is restricted to execute `func` once. Repeat calls to * the function will return the value of the first call. The `func` is executed * with the `this` binding of the created function. * * @static * @memberOf _ * @category Functions * @param {Function} func The function to restrict. * @returns {Function} Returns the new restricted function. * @example * * var initialize = _.once(createApplication); * initialize(); * initialize(); * // `initialize` executes `createApplication` once */ function once(func) { var ran, result; return function() { if (ran) { return result; } ran = true; result = func.apply(this, arguments); // clear the `func` variable so the function may be garbage collected func = null; return result; }; } /** * Creates a function that, when called, invokes `func` with any additional * `partial` arguments prepended to those passed to the new function. This * method is similar to `_.bind`, except it does **not** alter the `this` binding. * * @static * @memberOf _ * @category Functions * @param {Function} func The function to partially apply arguments to. * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. * @returns {Function} Returns the new partially applied function. * @example * * var greet = function(greeting, name) { return greeting + ' ' + name; }; * var hi = _.partial(greet, 'hi'); * hi('moe'); * // => 'hi moe' */ function partial(func) { return createBound(func, nativeSlice.call(arguments, 1)); } /** * This method is similar to `_.partial`, except that `partial` arguments are * appended to those passed to the new function. * * @static * @memberOf _ * @category Functions * @param {Function} func The function to partially apply arguments to. * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. * @returns {Function} Returns the new partially applied function. * @example * * var defaultsDeep = _.partialRight(_.merge, _.defaults); * * var options = { * 'variable': 'data', * 'imports': { 'jq': $ } * }; * * defaultsDeep(options, _.templateSettings); * * options.variable * // => 'data' * * options.imports * // => { '_': _, 'jq': $ } */ function partialRight(func) { return createBound(func, nativeSlice.call(arguments, 1), null, indicatorObject); } /** * Creates a function that, when executed, will only call the `func` function * at most once per every `wait` milliseconds. Pass an `options` object to * indicate that `func` should be invoked on the leading and/or trailing edge * of the `wait` timeout. Subsequent calls to the throttled function will * return the result of the last `func` call. * * Note: If `leading` and `trailing` options are `true`, `func` will be called * on the trailing edge of the timeout only if the the throttled function is * invoked more than once during the `wait` timeout. * * @static * @memberOf _ * @category Functions * @param {Function} func The function to throttle. * @param {Number} wait The number of milliseconds to throttle executions to. * @param {Object} options The options object. * [leading=true] A boolean to specify execution on the leading edge of the timeout. * [trailing=true] A boolean to specify execution on the trailing edge of the timeout. * @returns {Function} Returns the new throttled function. * @example * * var throttled = _.throttle(updatePosition, 100); * jQuery(window).on('scroll', throttled); * * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, { * 'trailing': false * })); */ function throttle(func, wait, options) { var leading = true, trailing = true; if (options === false) { leading = false; } else if (isObject(options)) { leading = 'leading' in options ? options.leading : leading; trailing = 'trailing' in options ? options.trailing : trailing; } options = getObject(); options.leading = leading; options.maxWait = wait; options.trailing = trailing; var result = debounce(func, wait, options); releaseObject(options); return result; } /** * Creates a function that passes `value` to the `wrapper` function as its * first argument. Additional arguments passed to the function are appended * to those passed to the `wrapper` function. The `wrapper` is executed with * the `this` binding of the created function. * * @static * @memberOf _ * @category Functions * @param {Mixed} value The value to wrap. * @param {Function} wrapper The wrapper function. * @returns {Function} Returns the new function. * @example * * var hello = function(name) { return 'hello ' + name; }; * hello = _.wrap(hello, function(func) { * return 'before, ' + func('moe') + ', after'; * }); * hello(); * // => 'before, hello moe, after' */ function wrap(value, wrapper) { return function() { var args = [value]; push.apply(args, arguments); return wrapper.apply(this, args); }; } /*--------------------------------------------------------------------------*/ /** * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their * corresponding HTML entities. * * @static * @memberOf _ * @category Utilities * @param {String} string The string to escape. * @returns {String} Returns the escaped string. * @example * * _.escape('Moe, Larry & Curly'); * // => 'Moe, Larry & Curly' */ function escape(string) { return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar); } /** * This method returns the first argument passed to it. * * @static * @memberOf _ * @category Utilities * @param {Mixed} value Any value. * @returns {Mixed} Returns `value`. * @example * * var moe = { 'name': 'moe' }; * moe === _.identity(moe); * // => true */ function identity(value) { return value; } /** * Adds functions properties of `object` to the `lodash` function and chainable * wrapper. * * @static * @memberOf _ * @category Utilities * @param {Object} object The object of function properties to add to `lodash`. * @example * * _.mixin({ * 'capitalize': function(string) { * return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); * } * }); * * _.capitalize('moe'); * // => 'Moe' * * _('moe').capitalize(); * // => 'Moe' */ function mixin(object) { forEach(functions(object), function(methodName) { var func = lodash[methodName] = object[methodName]; lodash.prototype[methodName] = function() { var value = this.__wrapped__, args = [value]; push.apply(args, arguments); var result = func.apply(lodash, args); return (value && typeof value == 'object' && value === result) ? this : new lodashWrapper(result); }; }); } /** * Reverts the '_' variable to its previous value and returns a reference to * the `lodash` function. * * @static * @memberOf _ * @category Utilities * @returns {Function} Returns the `lodash` function. * @example * * var lodash = _.noConflict(); */ function noConflict() { context._ = oldDash; return this; } /** * Converts the given `value` into an integer of the specified `radix`. * If `radix` is `undefined` or `0`, a `radix` of `10` is used unless the * `value` is a hexadecimal, in which case a `radix` of `16` is used. * * Note: This method avoids differences in native ES3 and ES5 `parseInt` * implementations. See http://es5.github.com/#E. * * @static * @memberOf _ * @category Utilities * @param {String} value The value to parse. * @param {Number} [radix] The radix used to interpret the value to parse. * @returns {Number} Returns the new integer value. * @example * * _.parseInt('08'); * // => 8 */ var parseInt = nativeParseInt(whitespace + '08') == 8 ? nativeParseInt : function(value, radix) { // Firefox and Opera still follow the ES3 specified implementation of `parseInt` return nativeParseInt(isString(value) ? value.replace(reLeadingSpacesAndZeros, '') : value, radix || 0); }; /** * Produces a random number between `min` and `max` (inclusive). If only one * argument is passed, a number between `0` and the given number will be returned. * * @static * @memberOf _ * @category Utilities * @param {Number} [min=0] The minimum possible value. * @param {Number} [max=1] The maximum possible value. * @returns {Number} Returns a random number. * @example * * _.random(0, 5); * // => a number between 0 and 5 * * _.random(5); * // => also a number between 0 and 5 */ function random(min, max) { if (min == null && max == null) { max = 1; } min = +min || 0; if (max == null) { max = min; min = 0; } else { max = +max || 0; } var rand = nativeRandom(); return (min % 1 || max % 1) ? min + nativeMin(rand * (max - min + parseFloat('1e-' + ((rand +'').length - 1))), max) : min + floor(rand * (max - min + 1)); } /** * Resolves the value of `property` on `object`. If `property` is a function, * it will be invoked with the `this` binding of `object` and its result returned, * else the property value is returned. If `object` is falsey, then `undefined` * is returned. * * @static * @memberOf _ * @category Utilities * @param {Object} object The object to inspect. * @param {String} property The property to get the value of. * @returns {Mixed} Returns the resolved value. * @example * * var object = { * 'cheese': 'crumpets', * 'stuff': function() { * return 'nonsense'; * } * }; * * _.result(object, 'cheese'); * // => 'crumpets' * * _.result(object, 'stuff'); * // => 'nonsense' */ function result(object, property) { var value = object ? object[property] : undefined; return isFunction(value) ? object[property]() : value; } /** * A micro-templating method that handles arbitrary delimiters, preserves * whitespace, and correctly escapes quotes within interpolated code. * * Note: In the development build, `_.template` utilizes sourceURLs for easier * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl * * For more information on precompiling templates see: * http://lodash.com/#custom-builds * * For more information on Chrome extension sandboxes see: * http://developer.chrome.com/stable/extensions/sandboxingEval.html * * @static * @memberOf _ * @category Utilities * @param {String} text The template text. * @param {Object} data The data object used to populate the text. * @param {Object} options The options object. * escape - The "escape" delimiter regexp. * evaluate - The "evaluate" delimiter regexp. * interpolate - The "interpolate" delimiter regexp. * sourceURL - The sourceURL of the template's compiled source. * variable - The data object variable name. * @returns {Function|String} Returns a compiled function when no `data` object * is given, else it returns the interpolated text. * @example * * // using a compiled template * var compiled = _.template('hello <%= name %>'); * compiled({ 'name': 'moe' }); * // => 'hello moe' * * var list = '<% _.forEach(people, function(name) { %>
  • <%= name %>
  • <% }); %>'; * _.template(list, { 'people': ['moe', 'larry'] }); * // => '
  • moe
  • larry
  • ' * * // using the "escape" delimiter to escape HTML in data property values * _.template('<%- value %>', { 'value': '