From b18a7d7c4e893d518a2f4cff44787db149dc0e13 Mon Sep 17 00:00:00 2001 From: Irene Ros Date: Mon, 27 Aug 2012 00:49:06 -0400 Subject: [PATCH 01/12] Updated all code to use idAttribute. --- src/builder.js | 2 +- src/dataset.js | 44 +++++++++------ src/derived.js | 39 ++++++++++---- src/product.js | 2 +- src/sync.js | 13 ++--- src/view.js | 47 +++++++++------- test/index.html | 1 - test/unit/core.js | 34 ++++++++++++ test/unit/dataset.js | 64 ++++++++++++++++++++++ test/unit/derived.js | 101 +++++++++++++++++++++++++++++++++- test/unit/events.js | 24 ++++++++- test/unit/helpers.js | 40 ++++++++++++++ test/unit/importers.js | 62 ++++++++++++++++++++- test/unit/products.js | 13 ++++- test/unit/views.js | 119 +++++++++++++++++++++++++++++++++++++++++ 15 files changed, 546 insertions(+), 59 deletions(-) diff --git a/src/builder.js b/src/builder.js index 9d68f4c..7e03ac1 100644 --- a/src/builder.js +++ b/src/builder.js @@ -76,7 +76,7 @@ // cache the row id positions in both directions. // iterate over the _id column and grab the row ids - _.each(dataset._columns[dataset._columnPositionByName._id].data, function(id, index) { + _.each(dataset._columns[dataset._columnPositionByName[dataset.idAttribute]].data, function(id, index) { dataset._rowPositionById[id] = index; dataset._rowIdByPosition.push(id); }, dataset); diff --git a/src/dataset.js b/src/dataset.js index d6a5795..14768a6 100644 --- a/src/dataset.js +++ b/src/dataset.js @@ -73,6 +73,8 @@ Version 0.0.1.2 this.syncable = true; } + this.idAttribute = options.idAttribute || '_id'; + // initialize importer from options or just create a blank // one for now, we'll detect it later. this.importer = options.importer || null; @@ -264,7 +266,7 @@ Version 0.0.1.2 toAdd.push( row ); } else { toUpdate.push( row ); - var oldRow = this.rowById(this.column('_id').data[rowIndex])._id; + var oldRow = this.rowById(this.column(this.idAttribute).data[rowIndex])[this.idAttribute]; this.update(oldRow, row); } }, this); @@ -423,7 +425,7 @@ Version 0.0.1.2 _addIdColumn : function( count ) { // if we have any data, generate actual ids. - if (!_.isUndefined(this.column("_id"))) { + if (!_.isUndefined(this.column(this.idAttribute))) { return; } @@ -435,23 +437,27 @@ Version 0.0.1.2 } // add the id column - this.addColumn({ name: "_id", type : "number", data : ids }); + var idCol = this.addColumn({ name: this.idAttribute, data : ids }); + // is this the default _id? if so set numeric type. Otherwise, + // detect data + if (this.idAttribute === "_id") { + idCol.type = "number"; + } // did we accidentally add it to the wrong place? (it should always be first.) - if (this._columnPositionByName._id !== 0) { + if (this._columnPositionByName[this.idAttribute] !== 0) { // we need to move it to the beginning and unshift all the other // columns - var idCol = this._columns[this._columnPositionByName._id], - oldIdColPos = this._columnPositionByName._id; + var oldIdColPos = this._columnPositionByName[this.idAttribute]; // move col back this._columns.splice(oldIdColPos, 1); this._columns.unshift(idCol); - this._columnPositionByName._id = 0; + this._columnPositionByName[this.idAttribute] = 0; _.each(this._columnPositionByName, function(pos, colName) { - if (colName !== "_id" && this._columnPositionByName[colName] < oldIdColPos) { + if (colName !== this.idAttribute && this._columnPositionByName[colName] < oldIdColPos) { this._columnPositionByName[colName]++; } }, this); @@ -478,8 +484,8 @@ Version 0.0.1.2 var deltas = []; _.each(rows, function(row) { - if (!row._id) { - row._id = _.uniqueId(); + if (!row[this.idAttribute]) { + row[this.idAttribute] = _.uniqueId(); } this._add(row, options); @@ -492,7 +498,7 @@ Version 0.0.1.2 }, this); if (this.syncable && !options.silent) { - var e = this._buildEvent(deltas); + var e = this._buildEvent(deltas, this); this.trigger('add', e ); this.trigger('change', e ); } @@ -513,7 +519,7 @@ Version 0.0.1.2 this.each(function(row, rowIndex) { if (filter(row)) { - rowsToRemove.push(row._id); + rowsToRemove.push(row[this.idAttribute]); deltas.push( { old: row } ); } }); @@ -526,7 +532,7 @@ Version 0.0.1.2 }, this); if (this.syncable && (!options || !options.silent)) { - var ev = this._buildEvent( deltas ); + var ev = this._buildEvent( deltas, this ); this.trigger('remove', ev ); this.trigger('change', ev ); } @@ -557,6 +563,11 @@ Version 0.0.1.2 _.each(newKeys, function(columnName) { + // check that we aren't trying to update the id column + if (columnName === this.idAttribute) { + throw "You can't update the id column"; + } + c = this.column(columnName); // check if we're trying to update a computed column. If so @@ -604,7 +615,9 @@ Version 0.0.1.2 }); } - deltas.push( { _id : row._id, old : row, changed : props } ); + var delta = { old : row, changed : props }; + delta[this.idAttribute] = row[this.idAttribute]; + deltas.push(delta); }, this); // do we just have a single id? array it up. @@ -634,7 +647,7 @@ Version 0.0.1.2 } if (this.syncable && (!options || !options.silent)) { - var ev = this._buildEvent( deltas ); + var ev = this._buildEvent( deltas, this ); this.trigger('update', ev ); this.trigger('change', ev ); } @@ -659,5 +672,6 @@ Version 0.0.1.2 } }); + }(this, _, moment)); diff --git a/src/derived.js b/src/derived.js index 9b02835..b47060d 100644 --- a/src/derived.js +++ b/src/derived.js @@ -23,6 +23,12 @@ // save parent dataset reference this.parent = options.parent; + // the id column in a derived dataset is always _id + // since there might not be a 1-1 mapping to each row + // but could be a 1-* mapping at which point a new id + // is needed. + this.idAttribute = "_id"; + // save the method we apply to bins. this.method = options.method; @@ -80,6 +86,12 @@ // copy over all columns this.eachColumn(function(columnName) { + + // don't try to compute a moving average on the id column. + if (columnName === this.idAttribute) { + throw "You can't compute a moving average on the id column"; + } + d.addColumn({ name : columnName, type : this.column(columnName).type, data : [] }); @@ -98,7 +110,9 @@ } // copy the ids - this.column("_id").data = this.parent.column("_id").data.slice(size-1, this.parent.length); + this.column(this.idAttribute).data = this.parent + .column(this.parent.idAttribute) + .data.slice(size-1, this.parent.length); // copy the columns we are NOT combining minus the sliced size. this.eachColumn(function(columnName, column, i) { @@ -117,7 +131,7 @@ var oidcol = this.column("_oids"); oidcol.data = []; for(var i = 0; i < this.length; i++) { - oidcol.data.push(this.parent.column("_id").data.slice(i, i+size)); + oidcol.data.push(this.parent.column(this.parent.idAttribute).data.slice(i, i+size)); } Miso.Builder.cacheRows(this); @@ -148,14 +162,15 @@ name : byColumn, type : parentByColumn.type }); + d.addColumn({ name : 'count', type : 'number' }); d.addColumn({ name : '_oids', type : 'mixed' }); Miso.Builder.cacheColumns(d); - var names = d._column(byColumn).data, - values = d._column('count').data, - _oids = d._column('_oids').data, - _ids = d._column('_id').data; + var names = d.column(byColumn).data, + values = d.column('count').data, + _oids = d.column('_oids').data, + _ids = d.column(d.idAttribute).data; function findIndex(names, datum, type) { var i; @@ -173,12 +188,12 @@ names.push( row[byColumn] ); _ids.push( _.uniqueId() ); values.push( 1 ); - _oids.push( [row._id] ); + _oids.push( [row[this.parent.idAttribute]] ); } else { values[index] += 1; - _oids[index].push( row._id ); + _oids[index].push( row[this.parent.idAttribute]); } - }); + }, d); Miso.Builder.cacheRows(d); return d; @@ -235,6 +250,8 @@ // host function var computeGroupBy = function() { + var self = this; + // clear row cache if it exists Miso.Builder.clearRowCache(this); @@ -266,7 +283,7 @@ // bin the values _.each(columns, function(columnToGroup) { var column = this.column(columnToGroup); - var idCol = this.column("_id"); + var idCol = this.column(this.idAttribute); column.data[categoryCount] = []; idCol.data[categoryCount] = _.uniqueId(); }, this); @@ -300,7 +317,7 @@ // save the original ids that created this group by? oidcol.data[binPos] = oidcol.data[binPos] || []; - oidcol.data[binPos].push(_.map(bin, function(row) { return row._id; })); + oidcol.data[binPos].push(_.map(bin, function(row) { return row[self.parent.idAttribute]; })); oidcol.data[binPos] = _.flatten(oidcol.data[binPos]); // compute the final value. diff --git a/src/product.js b/src/product.js index f75fda5..c9fa850 100644 --- a/src/product.js +++ b/src/product.js @@ -95,7 +95,7 @@ var delta = this._buildDelta(this.value, producer.call(_self)); this.value = delta.changed; if (_self.syncable) { - var event = this._buildEvent(delta); + var event = this._buildEvent(delta, this); if (!_.isUndefined(delta.old) && !options.silent && delta.old !== delta.changed) { this.trigger("change", event); } diff --git a/src/sync.js b/src/sync.js index e7b1d7d..b6cb72c 100644 --- a/src/sync.js +++ b/src/sync.js @@ -10,11 +10,12 @@ * deltas - array of deltas. * each delta: { changed : {}, old : {} } */ - Miso.Event = function(deltas) { + Miso.Event = function(deltas, dataset) { if (!_.isArray(deltas)) { deltas = [deltas]; } this.deltas = deltas; + this.dataset = dataset || null; }; _.extend(Miso.Event.prototype, { @@ -26,9 +27,9 @@ cols = _.chain(cols) .union(_.keys(delta.old), _.keys(delta.changed) ) .reject(function(col) { - return col === '_id'; - }).value(); - }); + return col === this.dataset.idAttribute; + }, this).value(); + }, this); return cols; } @@ -150,7 +151,7 @@ }; // Used to build event objects accross the application. - Miso.Events._buildEvent = function(delta) { - return new Miso.Event(delta); + Miso.Events._buildEvent = function(delta, dataset) { + return new Miso.Event(delta, dataset); }; }(this, _)); diff --git a/src/view.js b/src/view.js index 8b00082..956776f 100644 --- a/src/view.js +++ b/src/view.js @@ -163,12 +163,13 @@ this.syncable = true; } - // save filter - this.filter = { - columns : this._columnFilter(options.filter.columns || undefined), - rows : this._rowFilter(options.filter.rows || undefined) - }; + this.idAttribute = this.parent.idAttribute; + // save filter + this.filter = { }; + this.filter.columns = _.bind(this._columnFilter(options.filter.columns || undefined), this); + this.filter.rows = _.bind(this._rowFilter(options.filter.rows || undefined), this); + // initialize columns. this._columns = this._selectData(); @@ -189,7 +190,7 @@ _.each(deltas, function(d, deltaIndex) { // find row position based on delta _id - var rowPos = this._rowPositionById[d._id]; + var rowPos = this._rowPositionById[d[this.idAttribute]]; // ===== ADD NEW ROW @@ -234,10 +235,10 @@ // to such so that any child views, know how to interpet it. var newDelta = { - _id : d._id, old : this.rowByPosition(rowPos), changed : {} }; + newDelta[this.idAttribute] = d[this.idAttribute]; // replace the old delta with this delta event.deltas.splice(deltaIndex, 1, newDelta); @@ -330,7 +331,7 @@ if (_.isString(columnFilter) ) { columnFilter = [ columnFilter ]; } - columnFilter.push('_id'); + columnFilter.push(this.idAttribute); columnSelector = function(column) { return _.indexOf(columnFilter, column.name) === -1 ? false : true; }; @@ -361,9 +362,11 @@ rowSelector = rowFilter; } else { //array - rowSelector = function(row) { - return _.indexOf(rowFilter, row._id) === -1 ? false : true; - }; + rowSelector = _.bind(function(row) { + return _.indexOf(rowFilter, row[this.idAttribute]) === -1 ? + false : + true; + }, this); } return rowSelector; @@ -408,8 +411,8 @@ columnNames : function() { var cols = _.pluck(this._columns, 'name'); return _.reject(cols, function( colName ) { - return colName === '_id' || colName === '_oids'; - }); + return colName === this.idAttribute || colName === '_oids'; + }, this); }, /** @@ -568,8 +571,14 @@ // add row indeces to the cache this._rowIdByPosition = this._rowIdByPosition || (this._rowIdByPosition = []); this._rowPositionById = this._rowPositionById || (this._rowPositionById = {}); - this._rowIdByPosition.push(row._id); - this._rowPositionById[row._id] = this._rowIdByPosition.length; + + // if this row already exists, throw an error + if (typeof this._rowPositionById[row[this.idAttribute]] !== "undefined") { + throw "The id " + row[this.idAttribute] + " is not unique. The " + this.idAttribute + " column must be unique"; + } + + this._rowPositionById[row[this.idAttribute]] = this._rowIdByPosition.length; + this._rowIdByPosition.push(row[this.idAttribute]); // otherwise insert them in the right place. This is a somewhat // expensive operation. @@ -583,7 +592,7 @@ this.length++; for(i = 0; i < this.length; i++) { var row2 = this.rowByPosition(i); - if (_.isUndefined(row2._id) || this.comparator(row, row2) < 0) { + if (_.isUndefined(row2[this.idAttribute]) || this.comparator(row, row2) < 0) { _.each(this._columns, function(column) { insertAt(i, (row[column.name] ? row[column.name] : null), column.data); @@ -598,9 +607,9 @@ this._rowIdByPosition = []; this._rowPositionById = {}; this.each(function(row, i) { - this._rowIdByPosition.push(row._id); - this._rowPositionById[row._id] = i; - }); + this._rowIdByPosition.push(row[this.idAttribute]); + this._rowPositionById[row[this.idAttribute]] = i; + }, this); } return this; diff --git a/test/index.html b/test/index.html index e5d4145..08f1894 100644 --- a/test/index.html +++ b/test/index.html @@ -57,7 +57,6 @@ - diff --git a/test/unit/core.js b/test/unit/core.js index a61efc9..4dd0dc7 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -36,6 +36,7 @@ ok(!_.isUndefined(ds.movingAverage)); ok(!_.isUndefined(ds.groupBy)); ok(!_.isUndefined(ds.countBy)); + ok(!_.isUndefined(ds.idAttribute)); } function verifyDatasetPrototypeMethods(ds) { @@ -81,6 +82,7 @@ verifyDatasetPrototypeMethods(this); }}); }); + test("DataView ineritance", function() { var data = [ { a : 0, b : 0, c: 1}, @@ -187,6 +189,38 @@ }); }); + test("Basic fetch + deferred callback with custom idAttribute", function() { + var ds = new Miso.Dataset({ + data: [ + { one : 1, two : 4, three : 7 }, + { one : 2, two : 5, three : 8 }, + { one : 3, two : 6, three : 9 } + ], + idAttribute: "two" + }); + + _.when(ds.fetch()).then(function() { + equals(ds instanceof Miso.Dataset, true); + ok(_.isEqual(ds.columnNames(), ["one", "three"])); // no id columns + }); + }); + + test("Basic fetch + deferred callback with custom idAttribute with non unique values", 1, function() { + var ds = new Miso.Dataset({ + data: [ + { one : 1, two : 4, three : 7 }, + { one : 1, two : 5, three : 8 }, + { one : 3, two : 6, three : 9 } + ], + idAttribute: "one" + }); + + raises(function() { + ds.fetch(); + }); + + }); + test("Instantiation ready callback", function() { var ds = new Miso.Dataset({ data: [ diff --git a/test/unit/dataset.js b/test/unit/dataset.js index 7a88f23..a5b66a3 100644 --- a/test/unit/dataset.js +++ b/test/unit/dataset.js @@ -17,6 +17,21 @@ }); }); + + test("adding a row with custom idAttribute", function() { + var ds = Util.baseSampleCustomID(); + ds.add( { one: 100 } ); + + equals(ds._columns[1].data.length, 4, "row adding to 'one'"); + ok(!_.isUndefined(ds._rowIdByPosition[3]), "rowIdByPosition updated"); + _.each([1,2], function(i) { + equals(ds._columns[i].data.length, 4, "column length increased on "+ds._columns[i].name); + strictEqual(ds._columns[i].data[3], null, "null added to column "+ds._columns[i].name); + }); + + ok(_.isEqual(ds.rowById(100), { one : 100, two : null, three : null })); + }); + test("adding a row with wrong types", function() { var ds = Util.baseSample(); raises(function() { @@ -51,6 +66,16 @@ equals(ds.length, 2); }); + test("removing a row with an id with custom idAttribute", function() { + var ds = Util.baseSampleCustomID(); + var firstRowId = ds.rowByPosition(0).one; + + ds.remove(firstRowId); + strictEqual( ds._rowPositionById[firstRowId], undefined ); + ok( ds._rowIdByPosition[0] !== firstRowId ); + equals(ds.length, 2); + }); + test("upating a row with an incorrect type", function() { var ds = Util.baseSample(); _.each(['a', []], function(value) { @@ -70,6 +95,27 @@ }); }); + test("updating a row with custom idAttribute (non id column)", function() { + var ds = Util.baseSampleCustomID(); + ds._columns[1].type = 'untyped'; + var firstRowId = ds.rowByPosition(0).one; + + _.each([100, 'a', null, undefined, []], function(value) { + ds.update(firstRowId, { 'two': value } ); + equals(ds._columns[1].data[0], value, "value updated to "+value); + }); + }); + + test("updating a row with a custom idAttribute (updating id col)", 1, function() { + var ds = Util.baseSampleCustomID(); + var firstRowId = ds.rowByPosition(0).one; + + raises(function() { + ds.update(firstRowId, { one : 1 }); + }, "You can't update the id column"); + + }); + test("#105 - updating a row with a function", function() { var ds = Util.baseSample(); ds.update(function(row) { @@ -396,4 +442,22 @@ }); }); + + module("Custom idAttribute"); + + test("Specify custom idAttribute", function() { + var ds = new Miso.Dataset({ + data: { columns : [ + { name : "one", data : [1,2,3] }, + { name : "two", data : [10,20,30] } + ]}, + strict: true, + idAttribute: 'one' + }); + ds.fetch().then(function() { + ok(_.isEqual(ds.rowById(1), { one : 1, two : 10 })); + ok(_.isEqual(ds.rowById(2), { one : 2, two : 20 })); + ok(_.isEqual(ds.rowById(3), { one : 3, two : 30 })); + }); + }); }(this)); \ No newline at end of file diff --git a/test/unit/derived.js b/test/unit/derived.js index ada119d..451f207 100644 --- a/test/unit/derived.js +++ b/test/unit/derived.js @@ -44,6 +44,34 @@ }}); }); + + test("Counting rows with custom idAttribute", function() { + var ds = new Miso.Dataset({ + data : countData, + strict: true, + idAttribute : "things" + }).fetch({ success :function() { + + var counted = this.countBy('category'), + aCount = counted.rows(function(row) { + return row.category === 'a'; + }).rowByPosition(0).count, + bCount = counted.rows(function(row) { + return row.category === 'b'; + }).rowByPosition(0).count, + nullCount = counted.rows(function(row) { + return row.category === null; + }).rowByPosition(0).count; + + equals(4, counted.columns().length); + equals(4, aCount); + equals(2, bCount); + equals(1, nullCount); + + // equals(ma.length, this.length - 2); + }}); + }); + test("Counting rows with moment objs", function() { var ds = new Miso.Dataset({ data : [ @@ -96,6 +124,7 @@ }).fetch({ success :function() { var ma = this.movingAverage(["A", "B", "C"], 3); + equals(ma.length, this.length - 2); ok(_.isEqual(ma.column("A").data, _.movingAvg(this.column("A").data, 3))); ok(_.isEqual(ma.column("B").data, _.movingAvg(this.column("B").data, 3)), "Actual" + ma.column("B").data + " ,expected: " + _.movingAvg(this.column("B").data, 3)); @@ -103,7 +132,35 @@ }}); }); - test("Singe column moving average", function() { + test("Basic Moving Average custom idAttribute", function() { + var ds = new Miso.Dataset({ + data : getMovingAverageData(), + strict: true, + idAttribute : "A" + }).fetch({ success :function() { + + var ma = this.movingAverage(["B", "C"], 3); + equals(ma.length, this.length - 2); + ok(_.isEqual(ma.column("B").data, _.movingAvg(this.column("B").data, 3)), "Actual" + ma.column("B").data + " ,expected: " + _.movingAvg(this.column("B").data, 3)); + ok(_.isEqual(ma.column("C").data, _.movingAvg(this.column("C").data, 3))); + }}); + }); + + test("Basic Moving Average custom idAttribute should fail when including id col", 1, function() { + var ds = new Miso.Dataset({ + data : getMovingAverageData(), + strict: true, + idAttribute : "A" + }).fetch({ success :function() { + + raises(function(){ + var ma = this.movingAverage(["A","B"], 3); + }); + + }}); + }); + + test("Single column moving average", function() { var ds = new Miso.Dataset({ data : getMovingAverageData(), strict: true @@ -223,6 +280,22 @@ }); }); + test("base group by with custom idAttribute", function() { + + var ds = new Miso.Dataset({ + data : getData(), + strict: true, + idAttribute: "count" + }); + + _.when(ds.fetch()).then(function(){ + var groupedData = ds.groupBy("state", ["anothercount"]); + + ok(_.isEqual(groupedData.column("state").data, ["AZ", "MA"]), "states correct"); + ok(_.isEqual(groupedData.column("anothercount").data, [60,150]), "anothercounts correct"); + }); + }); + test("base group by syncable update", function() { var ds = new Miso.Dataset({ @@ -245,6 +318,32 @@ }); }); + test("base group by syncable update with custom idAttribute", function() { + + var ds = new Miso.Dataset({ + data : getData(), + strict: true, + sync : true, + idAttribute: "count" + }); + + _.when(ds.fetch()).then(function(){ + var groupedData = ds.groupBy("state", ["anothercount"]); + var rowid = ds._columns[0].data[0]; + + ds.update(rowid, { + state : "MN" + }); + + // TODO: the count column get overwritten since these are new rows... so it really + // is no longer a count column. It's just an id column. Not sure what to do about it + // at this point. Should it just go back to being an _id column? I think maybe? + ok(_.isEqual(groupedData.column("_oids").data, [[1], [2,3], [4,5,6]]), "oids correct"); + ok(_.isEqual(groupedData.column("state").data, ["MN", "AZ", "MA"]), "states correct"); + ok(_.isEqual(groupedData.column("anothercount").data, [10,50,150]), "anothercounts correct"); + }); + }); + test("base group by syncable add (existing category)", function() { var ds = new Miso.Dataset({ diff --git a/test/unit/events.js b/test/unit/events.js index 7797a62..38e225e 100644 --- a/test/unit/events.js +++ b/test/unit/events.js @@ -46,7 +46,6 @@ }); module("Event Object"); - test("affectedColumns for add event", function() { var ds = new Miso.Dataset({ @@ -104,4 +103,27 @@ }); + test("affectedColumns for update event with custom idAttribute", function() { + + var ds = new Miso.Dataset({ + data: { columns : [ + { name: "one", data: [1,2] }, + { name: "two", data: [4,5] } + ]}, + idAttribute : "two", + strict: true, + sync : true + }); + ds.fetch({ success: function() { + this.bind('change', function(event) { + equals( event.affectedColumns().length, 1); + ok( event.affectedColumns()[0] === 'one' ); + }); + } + }); + + ds.update( ds.column('two').data[0], {one: 9} ); + + }); + }(this)); diff --git a/test/unit/helpers.js b/test/unit/helpers.js index 13ecdb4..104abdb 100644 --- a/test/unit/helpers.js +++ b/test/unit/helpers.js @@ -22,6 +22,25 @@ return ds; }; + Util.baseSampleCustomID = function() { + var ds = null; + + new Miso.Dataset({ + data: { columns : [ + { name : "one", data : [1, 2, 3] }, + { name : "two", data : [4, 5, 6] }, + { name : "three", data : [7, 8, 9] } + ] }, + strict: true, + idAttribute: "one" + }).fetch({ + success : function() { + ds = this; + } + }); + return ds; + }; + Util.baseSyncingSample = function() { var ds = null; @@ -42,4 +61,25 @@ return ds; }; + Util.baseSyncingSampleCustomidAttribute = function() { + var ds = null; + + new Miso.Dataset({ + data: { columns : [ + { name : "one", data : [1, 2, 3] }, + { name : "two", data : [4, 5, 6] }, + { name : "three", data : [7, 8, 9] } + ] }, + idAttribute : "one", + strict: true, + sync : true + }).fetch({ + success : function() { + ds = this; + } + }); + return ds; + }; + + }(this)); \ No newline at end of file diff --git a/test/unit/importers.js b/test/unit/importers.js index 62b380f..eb894e2 100644 --- a/test/unit/importers.js +++ b/test/unit/importers.js @@ -30,7 +30,11 @@ ok(strictData._columnPositionByName[column.name] === i, "proper column position has been set"); }); - checkColumnTypes(strictData); + if (strictData.idAttribute !== "_id") { + checkColumnTypesCustomidAttribute(strictData); + } else { + checkColumnTypes(strictData); + } } function checkColumnTypes(strictData) { @@ -47,6 +51,19 @@ ok(strictData._column('numeric_value').type === "number", "numeric_value is numeric type"); } + function checkColumnTypesCustomidAttribute(strictData) { + + // check data size + ok(strictData.length === 24, "there are 24 rows"); + ok(strictData._columns.length === 4, "there are 5 columns"); + + // check column types + ok(strictData._column('character').type === "string", "character is string type"); + ok(strictData._column('name').type === "string", "name is string type"); + ok(strictData._column('is_modern').type === "boolean", "is_modern is boolean type"); + ok(strictData._column('numeric_value').type === "number", "numeric_value is numeric type"); + } + test("Basic Strict Import through Dataset API", 47, function() { var ds = new Miso.Dataset({ data : Miso.alphabet_strict, @@ -58,6 +75,18 @@ }); }); + test("Basic Strict Import through Dataset API with custom idAttribute", 44, function() { + var ds = new Miso.Dataset({ + data : Miso.alphabet_strict, + strict: true, + idAttribute: "character" + }); + _.when(ds.fetch()).then(function(){ + verifyImport(Miso.alphabet_strict, ds); + equals(typeof ds.columns, "function", "columns is the function, not the columns obj"); + }); + }); + module("Column creation, coercion & type setting"); test("Manually creating a column", function() { @@ -149,6 +178,22 @@ stop(); }); + test("Basic json url fetch through Dataset API with custom idAttribute", 43, function() { + var url = "data/alphabet_strict.json"; + var ds = new Miso.Dataset({ + url : url, + jsonp : false, + idAttribute : "name", + strict: true, + ready : function() { + verifyImport({}, this); + start(); + } + }); + ds.fetch(); + stop(); + }); + test("Basic json url fetch through Dataset API + url is a function", 46, function() { var ds = new Miso.Dataset({ url : function() { @@ -391,6 +436,21 @@ } }); + test("Delimited CR characters caught", 2, function() { + var ds = new Miso.Dataset({ + url : "data/offending.csv", + delimiter : "," + }); + stop(); + + ds.fetch().then(function() { + ok(ds.length === 71); + ok(ds._columns.length === 31); + + start(); + }); + }); + module("Google Spreadsheet Support"); function verifyGoogleSpreadsheet(d, obj) { diff --git a/test/unit/products.js b/test/unit/products.js index 1e3705f..adb6ae6 100644 --- a/test/unit/products.js +++ b/test/unit/products.js @@ -14,6 +14,15 @@ }); }); + test("Basic Sum Product with custom idAttribute", function() { + var ds = Util.baseSyncingSampleCustomidAttribute(); + + _.each(ds._columns, function(column){ + var sum = ds.sum(column.name); + ok(sum.val() === _.sum(column.data), "sum is correct for column "+ column.name); + }); + }); + test("Basic Sum Product Non Syncable", function() { var ds = Util.baseSample(); @@ -190,7 +199,7 @@ }); }); - test("Time Min Product Non Syncable", function() { + test("Time Min Product Non Syncable", 2, function() { var ds = new Miso.Dataset({ data : [ { "one" : 1, "t" : "2010/01/13" }, @@ -202,7 +211,7 @@ ] }); - _.when(ds.fetch(), function(){ + _.when(ds.fetch()).then(function(){ equals(ds.column("t").type, "time"); equals(ds.min("t").valueOf(), ds.column("t").data[0].valueOf()); }); diff --git a/test/unit/views.js b/test/unit/views.js index 4b49980..41d854d 100644 --- a/test/unit/views.js +++ b/test/unit/views.js @@ -106,6 +106,14 @@ }); }); + test("Basic View creation with custom idAttribute", function() { + var ds = Util.baseSampleCustomID(); + var view = ds.where({}); + _.each(ds._columns, function(column, i) { + ok(_.isEqual(ds._columns[i].data, view._columns[i].data), "data has been copied"); + }); + }); + test("One Row Filter View creation", function() { var ds = Util.baseSample(); @@ -118,6 +126,17 @@ }); }); + test("One Row Filter View creation with custom idAttribute", function() { + var ds = Util.baseSampleCustomID(); + var view = ds.where({ + rows : [ds._columns[0].data[0]] + }); + + _.each(ds._columns, function(column, i) { + ok(_.isEqual(ds._columns[i].data.slice(0, 1), view._columns[i].data), "data has been copied"); + }); + }); + test("One Row Filter View creation with short syntax", function() { var ds = Util.baseSample(); var view = ds.where(function(row) { @@ -129,6 +148,17 @@ }); }); + test("One Row Filter View creation with short syntax with custom idAttribute", function() { + var ds = Util.baseSampleCustomID(); + var view = ds.where(function(row) { + return row.one === ds._columns[0].data[0]; + }); + + _.each(ds._columns, function(column, i) { + ok(_.isEqual(ds._columns[i].data.slice(0, 1), view._columns[i].data), "data has been copied"); + }); + }); + test("Two Row Filter View creation", function() { var ds = Util.baseSample(); var view = ds.where({ @@ -140,6 +170,17 @@ }); }); + test("Two Row Filter View creation with custom idAttribute", function() { + var ds = Util.baseSampleCustomID(); + var view = ds.where({ + rows : [ds.column('one').data[0], ds.column('one').data[1]] + }); + + _.each(ds._columns, function(column, i) { + ok(_.isEqual(ds._columns[i].data.slice(0, 2), view._columns[i].data), "data has been copied"); + }); + }); + test("Function Row Filter View creation ", function() { var ds = Util.baseSample(); var view = ds.where({ @@ -153,6 +194,19 @@ }); }); + test("Function Row Filter View creation with custom idAttribute", function() { + var ds = Util.baseSampleCustomID(); + var view = ds.where({ + rows : function(row) { + return row.one === ds._columns[0].data[0]; + } + }); + + _.each(ds._columns, function(column, i) { + ok(_.isEqual(ds._columns[i].data.slice(0, 1), view._columns[i].data), "data has been copied"); + }); + }); + test("Function Row Filter View creation with computed product", function() { var ds = Util.baseSample(); var view = ds.where({ @@ -166,6 +220,18 @@ equals(view.min(["three"]), 7); }); + test("Function Row Filter View creation with computed product with custom idAttribute", function() { + var ds = Util.baseSampleCustomID(); + var view = ds.where({ + rows : function(row) { + return true; + } + }); + + equals(view.mean("three"), 8); + equals(view.max("three"), 9); + equals(view.min(["three"]), 7); + }); test("Using string syntax for columns", function() { var ds = Util.baseSample(); @@ -178,6 +244,28 @@ }); }); + test("Using string syntax for columns with custom idAttribute (the id col)", function() { + var ds = Util.baseSampleCustomID(); + var view = ds.where({ columns : 'one' }); + equals(view._columns.length, 1, "one data columns + _id"); //one column + _id + _.each(view._columns, function(column, columnIndex) { + _.each(column.data, function(d, rowIndex) { + equals(d, ds._columns[columnIndex].data[rowIndex], "data matches parent"); + }); + }); + }); + + test("Using string syntax for columns with custom idAttribute (non id col)", function() { + var ds = Util.baseSampleCustomID(); + var view = ds.where({ columns : 'two' }); + equals(view._columns.length, 2, "one data columns + _id"); //one column + _id + _.each(view._columns, function(column, columnIndex) { + _.each(column.data, function(d, rowIndex) { + equals(d, ds._columns[columnIndex].data[rowIndex], "data matches parent"); + }); + }); + }); + test("Columns View creation", function() { var ds = Util.baseSample(); var view = ds.where({ columns : [ 'one', 'two' ]}); @@ -190,6 +278,18 @@ }); }); + test("Columns View creation with idAttribute", function() { + var ds = Util.baseSampleCustomID(); + var view = ds.where({ columns : [ 'one', 'two' ]}); + + equals(view._columns.length, 2, "two data columns + _id"); //one column + _id + _.each(view._columns, function(column, columnIndex) { + _.each(column.data, function(d, rowIndex) { + equals(d, ds._columns[columnIndex].data[rowIndex], "data matches parent"); + }); + }); + }); + test("Select by columns and rows", function() { var ds = Util.baseSample(); var view = ds.where({ @@ -204,11 +304,30 @@ }); }); + test("Select by columns and rows by idAttribute (id col)", function() { + var ds = Util.baseSampleCustomID(); + var view = ds.where({ + rows : [ds._columns[0].data[0], ds._columns[0].data[1]], + columns : [ 'one' ] + }); + + equals(view.length, 2, "view has two rows"); + equals(view._columns.length, 1, "view has one column"); //id column + data column + _.each(view._columns[0].data, function(d, rowIndex) { + equals(d, ds._columns[0].data[rowIndex], "data matches parent"); + }); + }); + test("get all column names minus the id col", function() { var ds = Util.baseSample(); ok(_.isEqual(ds.columnNames(), ["one", "two", "three"]), "All column names fetched"); }); + test("get all column names minus the id col custom idAttribute", function() { + var ds = Util.baseSampleCustomID(); + ok(_.isEqual(ds.columnNames(), [ "two", "three"]), "All column names fetched"); + }); + module("Views :: Rows Selection"); test("each", function() { From 3dc8a8b1ebc7b9f1e8db675b0e5bd12e1bdcda82 Mon Sep 17 00:00:00 2001 From: Alex Graul Date: Mon, 3 Sep 2012 11:58:54 -0300 Subject: [PATCH 02/12] fixes to mixed type comparison and tests for countBy --- src/types.js | 6 +++--- test/unit/derived.js | 31 ++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/types.js b/src/types.js index 0945174..d32c0dd 100644 --- a/src/types.js +++ b/src/types.js @@ -33,9 +33,9 @@ return true; }, compare : function(s1, s2) { - if (s1 < s2) { return -1; } - if (s1 > s2) { return 1; } - return 0; + if ( _.isEqual(s1, s2) ) { return 0; } + if (s1 < s2) { return -1;} + if (s1 > s2) { return 1; } }, numeric : function(v) { return v === null || _.isNaN(+v) ? null : +v; diff --git a/test/unit/derived.js b/test/unit/derived.js index 18570fd..45567c8 100644 --- a/test/unit/derived.js +++ b/test/unit/derived.js @@ -12,11 +12,40 @@ }, { name : "category", data : ['a','b','a','a','c','c', null,'a','b','c'], - type : "numeric" + type : "string" + }, + { name : "stuff", + data : [window, window, {}, {}, undefined, null, null, [], [1], 6], + type : "mixed" } ] }; + test("counting a mess", function() { + var ds = new Miso.Dataset({ + data : countData, + strict: true + }).fetch({ success :function() { + var counted = this.countBy('stuff'), + nullCount = counted.rows(function(row) { + return row.stuff === null; + }).rowByPosition(0).count, + objCount = counted.rows(function(row) { + return _.isEqual(row.stuff, {}); + }).rowByPosition(0).count, + arrCount = counted.rows(function(row) { + return _.isEqual(row.stuff, []); + }).rowByPosition(0).count; + + equals(6, counted.columns().length); + equals(3, nullCount); + equals(2, objCount); + equals(1, arrCount); + + }}); + + + }); test("Counting rows", function() { var ds = new Miso.Dataset({ From 23ed72cdc82ec8097cbd35c158b7356386947c2f Mon Sep 17 00:00:00 2001 From: Irene Ros Date: Mon, 3 Sep 2012 17:18:43 +0200 Subject: [PATCH 03/12] Upgrading lodash to 0.6.1 --- lib/lodash.js | 4672 ++++++++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 2724 insertions(+), 1950 deletions(-) diff --git a/lib/lodash.js b/lib/lodash.js index 1d248a6..c603e64 100644 --- a/lib/lodash.js +++ b/lib/lodash.js @@ -1,5 +1,5 @@ /*! - * Lo-Dash v0.3.2 + * Lo-Dash v0.6.1 * Copyright 2012 John-David Dalton * Based on Underscore.js 1.3.3, copyright 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. * @@ -8,33 +8,80 @@ ;(function(window, undefined) { 'use strict'; + /** + * Used to cache the last `_.templateSettings.evaluate` delimiter to avoid + * unnecessarily assigning `reEvaluateDelimiter` a new generated regexp. + * Assigned in `_.template`. + */ + var lastEvaluateDelimiter; + + /** + * Used to cache the last template `options.variable` to avoid unnecessarily + * assigning `reDoubleVariable` a new generated regexp. Assigned in `_.template`. + */ + var lastVariable; + + /** + * Used to match potentially incorrect data object references, like `obj.obj`, + * in compiled templates. Assigned in `_.template`. + */ + var reDoubleVariable; + + /** + * Used to match "evaluate" delimiters, including internal delimiters, + * in template text. Assigned in `_.template`. + */ + var reEvaluateDelimiter; + /** Detect free variable `exports` */ var freeExports = typeof exports == 'object' && exports && (typeof global == 'object' && global && global == global.global && (window = global), exports); - /** - * Detect the JScript [[DontEnum]] bug: - * In IE < 9 an objects own properties, shadowing non-enumerable ones, are - * made non-enumerable as well. - */ - var hasDontEnumBug = !{ 'valueOf': 0 }.propertyIsEnumerable('valueOf'); + /** Native prototype shortcuts */ + var ArrayProto = Array.prototype, + BoolProto = Boolean.prototype, + ObjectProto = Object.prototype, + NumberProto = Number.prototype, + StringProto = String.prototype; /** Used to generate unique IDs */ var idCounter = 0; + /** Used by `cachedContains` as the default size when optimizations are enabled for large arrays */ + var largeArraySize = 30; + /** Used to restore the original `_` reference in `noConflict` */ var oldDash = window._; + /** Used to detect delimiter values that should be processed by `tokenizeEvaluate` */ + var reComplexDelimiter = /[-+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/; + + /** Used to match HTML entities */ + var reEscapedHtml = /&(?:amp|lt|gt|quot|#x27);/g; + + /** 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 regexp flags from their coerced string values */ + var reFlags = /\w*$/; + + /** Used to insert the data object variable into compiled template source */ + var reInsertVariable = /(?:__e|__t = )\(\s*(?![\d\s"']|this\.)/g; + /** Used to detect if a method is native */ - var reNative = RegExp('^' + ({}.valueOf + '') - .replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&') - .replace(/valueOf|for [^\]]+/g, '.+?') + '$'); + var reNative = RegExp('^' + + (ObjectProto.valueOf + '') + .replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&') + .replace(/valueOf|for [^\]]+/g, '.+?') + '$' + ); - /** Used to match tokens in template text */ + /** Used to match internally used tokens in template text */ var reToken = /__token__(\d+)/g; - /** Used to match unescaped characters in HTML */ - var reUnescapedHtml = /[&<"']/g; + /** 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; @@ -54,25 +101,162 @@ /** Used to store tokenized template text snippets */ var tokenized = []; - /** Detect if sourceURL syntax is usable without erroring */ + /** Native method shortcuts */ + var concat = ArrayProto.concat, + hasOwnProperty = ObjectProto.hasOwnProperty, + push = ArrayProto.push, + propertyIsEnumerable = ObjectProto.propertyIsEnumerable, + slice = ArrayProto.slice, + toString = ObjectProto.toString; + + /* Native method shortcuts for methods with the same name as other `lodash` methods */ + var nativeBind = reNative.test(nativeBind = slice.bind) && nativeBind, + nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, + nativeIsFinite = window.isFinite, + nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys; + + /** `Object#toString` result shortcuts */ + var argsClass = '[object Arguments]', + arrayClass = '[object Array]', + boolClass = '[object Boolean]', + dateClass = '[object Date]', + funcClass = '[object Function]', + numberClass = '[object Number]', + objectClass = '[object Object]', + regexpClass = '[object RegExp]', + stringClass = '[object String]'; + + /** Timer shortcuts */ + var clearTimeout = window.clearTimeout, + setTimeout = window.setTimeout; + + /** + * Detect the JScript [[DontEnum]] bug: + * + * In IE < 9 an objects own properties, shadowing non-enumerable ones, are + * made non-enumerable as well. + */ + var hasDontEnumBug; + + /** + * Detect if `Array#shift` and `Array#splice` augment array-like objects + * incorrectly: + * + * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()` + * and `splice()` functions that fail to remove the last element, `value[0]`, + * of array-like objects even though the `length` property is set to `0`. + * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` + * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. + */ + var hasObjectSpliceBug; + + /** Detect if own properties are iterated after inherited properties (IE < 9) */ + var iteratesOwnLast; + + /** Detect if an `arguments` object's indexes are non-enumerable (IE < 9) */ + var noArgsEnum = true; + + (function() { + var object = { '0': 1, 'length': 1 }, + props = []; + + function ctor() { this.x = 1; } + ctor.prototype = { 'valueOf': 1, 'y': 1 }; + for (var prop in new ctor) { props.push(prop); } + for (prop in arguments) { noArgsEnum = !prop; } + + hasDontEnumBug = (props + '').length < 4; + iteratesOwnLast = props[0] != 'x'; + hasObjectSpliceBug = (props.splice.call(object, 0, 1), object[0]); + }(1)); + + /** Detect if an `arguments` object's [[Class]] is unresolvable (Firefox < 4, IE < 9) */ + var noArgsClass = !isArguments(arguments); + + /** Detect if `Array#slice` cannot be used to convert strings to arrays (Opera < 10.52) */ + var noArraySliceOnStrings = slice.call('x')[0] != 'x'; + + /** + * Detect lack of support for accessing string characters by index: + * + * IE < 8 can't access characters by index and IE 8 can only access + * characters by index on string literals. + */ + var noCharByIndex = ('x'[0] + Object('x')[0]) != 'xx'; + + /** + * Detect if a node's [[Class]] is unresolvable (IE < 9) + * and that the JS engine won't error when attempting to coerce an object to + * a string without a `toString` property value of `typeof` "function". + */ + try { + var noNodeClass = ({ 'toString': 0 } + '', toString.call(window.document || 0) == objectClass); + } catch(e) { } + + /* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */ + var isBindFast = nativeBind && /\n|Opera/.test(nativeBind + toString.call(window.opera)); + + /* Detect if `Object.keys` exists and is inferred to be fast (IE, Opera, V8) */ + var isKeysFast = nativeKeys && /^.+$|true/.test(nativeKeys + !!window.attachEvent); + + /* Detect if strict mode, "use strict", is inferred to be fast (V8) */ + var isStrictFast = !isBindFast; + + /** + * Detect if sourceURL syntax is usable without erroring: + * + * The JS engine in Adobe products, like InDesign, will throw a syntax error + * when it encounters a single line comment beginning with the `@` symbol. + * + * The JS engine in Narwhal will generate the function `function anonymous(){//}` + * and throw a syntax error. + * + * Avoid comments beginning `@` symbols in IE because they are part of its + * non-standard conditional compilation support. + * http://msdn.microsoft.com/en-us/library/121hztk3(v=vs.94).aspx + */ try { - // Adobe's and Narwhal's JS engines will error - var useSourceURL = (Function('//@')(), true); + var useSourceURL = (Function('//@')(), !window.attachEvent); } catch(e){ } + /** Used to identify object classifications that are array-like */ + var arrayLikeClasses = {}; + arrayLikeClasses[boolClass] = arrayLikeClasses[dateClass] = arrayLikeClasses[funcClass] = + arrayLikeClasses[numberClass] = arrayLikeClasses[objectClass] = arrayLikeClasses[regexpClass] = false; + arrayLikeClasses[argsClass] = arrayLikeClasses[arrayClass] = arrayLikeClasses[stringClass] = true; + + /** Used to identify object classifications that `_.clone` supports */ + var cloneableClasses = {}; + cloneableClasses[argsClass] = cloneableClasses[funcClass] = false; + cloneableClasses[arrayClass] = cloneableClasses[boolClass] = cloneableClasses[dateClass] = + cloneableClasses[numberClass] = cloneableClasses[objectClass] = cloneableClasses[regexpClass] = + cloneableClasses[stringClass] = true; + /** - * Used to escape characters for inclusion in HTML. - * The `>` and `/` characters 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 (semi-related fun fact) + * 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 = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': "'" + }; + /** Used to determine if values are of the language type Object */ var objectTypes = { 'boolean': false, @@ -80,7 +264,8 @@ 'object': true, 'number': false, 'string': false, - 'undefined': false + 'undefined': false, + 'unknown': true }; /** Used to escape characters for inclusion in compiled string literals */ @@ -94,39 +279,6 @@ '\u2029': 'u2029' }; - /** Object#toString result shortcuts */ - var arrayClass = '[object Array]', - boolClass = '[object Boolean]', - dateClass = '[object Date]', - funcClass = '[object Function]', - numberClass = '[object Number]', - regexpClass = '[object RegExp]', - stringClass = '[object String]'; - - /** Native prototype shortcuts */ - var ArrayProto = Array.prototype, - ObjectProto = Object.prototype; - - /** Native method shortcuts */ - var concat = ArrayProto.concat, - hasOwnProperty = ObjectProto.hasOwnProperty, - push = ArrayProto.push, - slice = ArrayProto.slice, - toString = ObjectProto.toString; - - /* Used if `Function#bind` exists and is inferred to be fast (i.e. all but V8) */ - var nativeBind = reNative.test(nativeBind = slice.bind) && - /\n|Opera/.test(nativeBind + toString.call(window.opera)) && nativeBind; - - /* Native method shortcuts for methods with the same name as other `lodash` methods */ - var nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, - nativeIsFinite = window.isFinite, - nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys; - - /** Timer shortcuts */ - var clearTimeout = window.clearTimeout, - setTimeout = window.setTimeout; - /*--------------------------------------------------------------------------*/ /** @@ -158,8 +310,9 @@ } /** - * By default, Lo-Dash uses ERB-style template delimiters, change the - * following template settings to use alternative delimiters. + * 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 _ @@ -201,7 +354,7 @@ * @memberOf _.templateSettings * @type String */ - 'variable': 'obj' + 'variable': '' }; /*--------------------------------------------------------------------------*/ @@ -214,8 +367,13 @@ * @returns {String} Returns the interpolated text. */ var iteratorTemplate = template( + // conditional strict mode + '<% if (useStrict) { %>\'use strict\';\n<% } %>' + + + // the `iteratee` may be reassigned by the `top` snippet + 'var index, value, iteratee = <%= firstArg %>, ' + // assign the `result` variable an initial value - 'var index, result<% if (init) { %> = <%= init %><% } %>;\n' + + 'result<% if (init) { %> = <%= init %><% } %>;\n' + // add code to exit early or do so if the first argument is falsey '<%= exit %>;\n' + // add code after the exit snippet but before the iteration branches @@ -223,29 +381,38 @@ // the following branch is for iterating arrays and array-like objects '<% if (arrayBranch) { %>' + - 'var length = <%= firstArg %>.length; index = -1;' + - ' <% if (objectBranch) { %>\nif (length === length >>> 0) {<% } %>\n' + + 'var length = iteratee.length; index = -1;' + + ' <% if (objectBranch) { %>\nif (length > -1 && length === length >>> 0) {<% } %>' + + + // add support for accessing string characters by index if needed + ' <% if (noCharByIndex) { %>\n' + + ' if (toString.call(iteratee) == stringClass) {\n' + + ' iteratee = iteratee.split(\'\')\n' + + ' }' + + ' <% } %>\n' + + ' <%= arrayBranch.beforeLoop %>;\n' + - ' while (<%= arrayBranch.loopExp %>) {\n' + - ' <%= arrayBranch.inLoop %>;\n' + + ' while (++index < length) {\n' + + ' value = iteratee[index];\n' + + ' <%= arrayBranch.inLoop %>\n' + ' }' + - ' <% if (objectBranch) { %>\n}\n<% }' + - '}' + + ' <% if (objectBranch) { %>\n}<% } %>' + + '<% } %>' + // the following branch is for iterating an object's own/inherited properties - 'if (objectBranch) {' + - ' if (arrayBranch) { %>else {\n<% }' + - ' if (!hasDontEnumBug) { %> var skipProto = typeof <%= iteratedObject %> == \'function\';\n<% } %>' + - ' <%= objectBranch.beforeLoop %>;\n' + - ' for (<%= objectBranch.loopExp %>) {' + - ' \n<%' + - ' if (hasDontEnumBug) {' + - ' if (useHas) { %> if (<%= hasExp %>) {\n <% } %>' + - ' <%= objectBranch.inLoop %>;<%' + - ' if (useHas) { %>\n }<% }' + - ' }' + - ' else {' + - ' %>' + + '<% if (objectBranch) { %>' + + ' <% if (arrayBranch) { %>\nelse {' + + + // add support for iterating over `arguments` objects if needed + ' <% } else if (noArgsEnum) { %>\n' + + ' var length = iteratee.length; index = -1;\n' + + ' if (length && isArguments(iteratee)) {\n' + + ' while (++index < length) {\n' + + ' value = iteratee[index += \'\'];\n' + + ' <%= objectBranch.inLoop %>\n' + + ' }\n' + + ' } else {' + + ' <% } %>' + // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 // (if the prototype or a property on the prototype has been set) @@ -253,30 +420,60 @@ // value to `true`. Because of this Lo-Dash standardizes on skipping // the the `prototype` property of functions regardless of its // [[Enumerable]] value. - ' if (!(skipProto && index == \'prototype\')<% if (useHas) { %> && <%= hasExp %><% } %>) {\n' + - ' <%= objectBranch.inLoop %>;\n' + - ' }' + - ' <% } %>\n' + + ' <% if (!hasDontEnumBug) { %>\n' + + ' var skipProto = typeof iteratee == \'function\' && \n' + + ' propertyIsEnumerable.call(iteratee, \'prototype\');\n' + + ' <% } %>' + + + // iterate own properties using `Object.keys` if it's fast + ' <% if (isKeysFast && useHas) { %>\n' + + ' var ownIndex = -1,\n' + + ' ownProps = objectTypes[typeof iteratee] ? nativeKeys(iteratee) : [],\n' + + ' length = ownProps.length;\n\n' + + ' <%= objectBranch.beforeLoop %>;\n' + + ' while (++ownIndex < length) {\n' + + ' index = ownProps[ownIndex];\n' + + ' <% if (!hasDontEnumBug) { %>if (!(skipProto && index == \'prototype\')) {\n <% } %>' + + ' value = iteratee[index];\n' + + ' <%= objectBranch.inLoop %>\n' + + ' <% if (!hasDontEnumBug) { %>}\n<% } %>' + + ' }' + + + // else using a for-in loop + ' <% } else { %>\n' + + ' <%= objectBranch.beforeLoop %>;\n' + + ' for (index in iteratee) {' + + ' <% if (!hasDontEnumBug || useHas) { %>\n if (<%' + + ' if (!hasDontEnumBug) { %>!(skipProto && index == \'prototype\')<% }' + + ' if (!hasDontEnumBug && useHas) { %> && <% }' + + ' if (useHas) { %>hasOwnProperty.call(iteratee, index)<% }' + + ' %>) {' + + ' <% } %>\n' + + ' value = iteratee[index];\n' + + ' <%= objectBranch.inLoop %>;\n' + + ' <% if (!hasDontEnumBug || useHas) { %>}\n<% } %>' + ' }' + + ' <% } %>' + // Because IE < 9 can't set the `[[Enumerable]]` attribute of an // existing property and the `constructor` property of a prototype // defaults to non-enumerable, Lo-Dash skips the `constructor` // property when it infers it's iterating over a `prototype` object. - ' <% if (hasDontEnumBug) { %>\n' + - ' var ctor = <%= iteratedObject %>.constructor;\n' + - ' <% for (var k = 0; k < 7; k++) { %>\n' + + ' <% if (hasDontEnumBug) { %>\n\n' + + ' var ctor = iteratee.constructor;\n' + + ' <% for (var k = 0; k < 7; k++) { %>\n' + ' index = \'<%= shadowed[k] %>\';\n' + ' if (<%' + ' if (shadowed[k] == \'constructor\') {' + - ' %>!(ctor && ctor.prototype === <%= iteratedObject %>) && <%' + - ' } %><%= hasExp %>) {\n' + - ' <%= objectBranch.inLoop %>;\n' + - ' }<%' + - ' }' + - ' }' + - ' if (arrayBranch) { %>\n}<% }' + - '} %>\n' + + ' %>!(ctor && ctor.prototype === iteratee) && <%' + + ' } %>hasOwnProperty.call(iteratee, index)) {\n' + + ' value = iteratee[index];\n' + + ' <%= objectBranch.inLoop %>\n' + + ' }' + + ' <% } %>' + + ' <% } %>' + + ' <% if (arrayBranch || noArgsEnum) { %>\n}<% } %>' + + '<% } %>\n' + // add code to the bottom of the iteration function '<%= bottom %>;\n' + @@ -286,7 +483,8 @@ /** * Reusable iterator options shared by - * `every`, `filter`, `find`, `forEach`, `forIn`, `forOwn`, `map`, `reject`, and `some`. + * `every`, `filter`, `find`, `forEach`, `forIn`, `forOwn`, `groupBy`, `map`, + * `reject`, `some`, and `sortBy`. */ var baseIteratorOptions = { 'args': 'collection, callback, thisArg', @@ -298,33 +496,68 @@ 'else if (thisArg) {\n' + ' callback = iteratorBind(callback, thisArg)\n' + '}', - 'inLoop': 'callback(collection[index], index, collection)' + 'inLoop': 'if (callback(value, index, collection) === false) return result' + }; + + /** Reusable iterator options for `countBy`, `groupBy`, and `sortBy` */ + var countByIteratorOptions = { + 'init': '{}', + 'top': + 'var prop;\n' + + 'if (typeof callback != \'function\') {\n' + + ' var valueProp = callback;\n' + + ' callback = function(value) { return value[valueProp] }\n' + + '}\n' + + 'else if (thisArg) {\n' + + ' callback = iteratorBind(callback, thisArg)\n' + + '}', + 'inLoop': + 'prop = callback(value, index, collection);\n' + + '(hasOwnProperty.call(result, prop) ? result[prop]++ : result[prop] = 1)' + }; + + /** Reusable iterator options for `drop` and `pick` */ + var dropIteratorOptions = { + 'useHas': false, + 'args': 'object, callback, thisArg', + 'init': '{}', + 'top': + 'var isFunc = typeof callback == \'function\';\n' + + 'if (!isFunc) {\n' + + ' var props = concat.apply(ArrayProto, arguments)\n' + + '} else if (thisArg) {\n' + + ' callback = iteratorBind(callback, thisArg)\n' + + '}', + 'inLoop': + 'if (isFunc\n' + + ' ? !callback(value, index, object)\n' + + ' : indexOf(props, index) < 0\n' + + ') result[index] = value' }; /** Reusable iterator options for `every` and `some` */ var everyIteratorOptions = { 'init': 'true', - 'inLoop': 'if (!callback(collection[index], index, collection)) return !result' + 'inLoop': 'if (!callback(value, index, collection)) return !result' }; /** Reusable iterator options for `defaults` and `extend` */ var extendIteratorOptions = { + 'useHas': false, + 'useStrict': false, 'args': 'object', 'init': 'object', 'top': - 'for (var source, sourceIndex = 1, length = arguments.length; sourceIndex < length; sourceIndex++) {\n' + - ' source = arguments[sourceIndex];\n' + - (hasDontEnumBug ? ' if (source) {' : ''), - 'loopExp': 'index in source', - 'useHas': false, - 'inLoop': 'object[index] = source[index]', - 'bottom': (hasDontEnumBug ? ' }\n' : '') + '}' + 'for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {\n' + + ' if (iteratee = arguments[argsIndex]) {', + 'inLoop': 'result[index] = value', + 'bottom': ' }\n}' }; - /** Reusable iterator options for `filter` and `reject` */ + /** Reusable iterator options for `filter`, `reject`, and `where` */ var filterIteratorOptions = { 'init': '[]', - 'inLoop': 'callback(collection[index], index, collection) && result.push(collection[index])' + 'inLoop': 'callback(value, index, collection) && result.push(value)' }; /** Reusable iterator options for `find`, `forEach`, `forIn`, and `forOwn` */ @@ -339,22 +572,61 @@ } }; - /** Reusable iterator options for `invoke`, `map`, and `pluck` */ + /** Reusable iterator options for `invoke`, `map`, `pluck`, and `sortBy` */ var mapIteratorOptions = { 'init': '', 'exit': 'if (!collection) return []', 'beforeLoop': { 'array': 'result = Array(length)', - 'object': 'result = []' + 'object': 'result = ' + (isKeysFast ? 'Array(length)' : '[]') }, 'inLoop': { - 'array': 'result[index] = callback(collection[index], index, collection)', - 'object': 'result.push(callback(collection[index], index, collection))' + 'array': 'result[index] = callback(value, index, collection)', + 'object': 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '(callback(value, index, collection))' } }; /*--------------------------------------------------------------------------*/ + /** + * Creates a new function optimized for searching large arrays for a given `value`, + * starting at `fromIndex`, using strict equality for comparisons, i.e. `===`. + * + * @private + * @param {Array} array The array to search. + * @param {Mixed} value The value to search for. + * @param {Number} [fromIndex=0] The index to start searching from. + * @param {Number} [largeSize=30] The length at which an array is considered large. + * @returns {Boolean} Returns `true` if `value` is found, else `false`. + */ + function cachedContains(array, fromIndex, largeSize) { + fromIndex || (fromIndex = 0); + + var length = array.length, + isLarge = (length - fromIndex) >= (largeSize || largeArraySize), + cache = isLarge ? {} : array; + + if (isLarge) { + // init value cache + var key, + index = fromIndex - 1; + + while (++index < length) { + // manually coerce `value` to string because `hasOwnProperty`, in some + // older versions of Firefox, coerces objects incorrectly + key = array[index] + ''; + (hasOwnProperty.call(cache, key) ? cache[key] : (cache[key] = [])).push(array[index]); + } + } + return function(value) { + if (isLarge) { + var key = value + ''; + return hasOwnProperty.call(cache, key) && indexOf(cache[key], value) > -1; + } + return indexOf(cache, value, fromIndex) > -1; + } + } + /** * Creates compiled iteration functions. The iteration function will be created * to iterate over only objects if the first argument of `options.args` is @@ -363,6 +635,12 @@ * @private * @param {Object} [options1, options2, ...] The compile options objects. * + * useHas - A boolean to specify whether or not to use `hasOwnProperty` checks + * in the object loop. + * + * useStrict - A boolean to specify whether or not to include the ES5 + * "use strict" directive. + * * args - A string of comma separated arguments the iteration function will * accept. * @@ -377,12 +655,6 @@ * beforeLoop - A string or object containing an "array" or "object" property * of code to execute before the array or object loops. * - * loopExp - A string or object containing an "array" or "object" property - * of code to execute as the array or object loop expression. - * - * useHas - A boolean to specify whether or not to use `hasOwnProperty` checks - * in the object loop. - * * inLoop - A string or object containing an "array" or "object" property * of code to execute in the array or object loops. * @@ -404,7 +676,7 @@ 'exit': '', 'init': '', 'top': '', - 'arrayBranch': { 'beforeLoop': '', 'loopExp': '++index < length' }, + 'arrayBranch': { 'beforeLoop': '' }, 'objectBranch': { 'beforeLoop': '' } }; @@ -413,12 +685,12 @@ for (prop in object) { value = (value = object[prop]) == null ? '' : value; // keep this regexp explicit for the build pre-process - if (/beforeLoop|loopExp|inLoop/.test(prop)) { + if (/beforeLoop|inLoop/.test(prop)) { if (typeof value == 'string') { value = { 'array': value, 'object': value }; } - data.arrayBranch[prop] = value.array; - data.objectBranch[prop] = value.object; + data.arrayBranch[prop] = value.array || ''; + data.objectBranch[prop] = value.object || ''; } else { data[prop] = value; } @@ -426,51 +698,57 @@ } // set additional template `data` values var args = data.args, - arrayBranch = data.arrayBranch, - objectBranch = data.objectBranch, firstArg = /^[^,]+/.exec(args)[0], - loopExp = objectBranch.loopExp, - iteratedObject = /\S+$/.exec(loopExp || firstArg)[0]; + useStrict = data.useStrict; data.firstArg = firstArg; data.hasDontEnumBug = hasDontEnumBug; - data.hasExp = 'hasOwnProperty.call(' + iteratedObject + ', index)'; - data.iteratedObject = iteratedObject; + data.isKeysFast = isKeysFast; + data.noArgsEnum = noArgsEnum; data.shadowed = shadowed; data.useHas = data.useHas !== false; + data.useStrict = useStrict == null ? isStrictFast : useStrict; + if (data.noCharByIndex == null) { + data.noCharByIndex = noCharByIndex; + } if (!data.exit) { data.exit = 'if (!' + firstArg + ') return result'; } - if (firstArg == 'object' || !arrayBranch.inLoop) { + if (firstArg != 'collection' || !data.arrayBranch.inLoop) { data.arrayBranch = null; } - if (!loopExp) { - objectBranch.loopExp = 'index in ' + iteratedObject; - } // create the function factory var factory = Function( - 'arrayClass, funcClass, hasOwnProperty, identity, iteratorBind, objectTypes, ' + - 'slice, stringClass, toString', - '"use strict"; return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' + 'arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn, ' + + 'hasOwnProperty, identity, indexOf, isArguments, isArray, isFunction, ' + + 'isPlainObject, iteratorBind, objectClass, objectTypes, nativeKeys, ' + + 'propertyIsEnumerable, slice, stringClass, toString', + 'var callee = function(' + args + ') {\n' + iteratorTemplate(data) + '\n};\n' + + 'return callee' ); // return the compiled function return factory( - arrayClass, funcClass, hasOwnProperty, identity, iteratorBind, objectTypes, - slice, stringClass, toString + arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn, + hasOwnProperty, identity, indexOf, isArguments, isArray, isFunction, + isPlainObject, iteratorBind, objectClass, objectTypes, nativeKeys, + propertyIsEnumerable, slice, stringClass, toString ); } /** - * Used by `sortBy()` to compare values of the array returned by `toSortable()`, - * sorting them in ascending order. + * 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 `-1` if `a` < `b`, `0` if `a` == `b`, or `1` if `a` > `b`. + * @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; @@ -480,11 +758,13 @@ if (b === undefined) { return -1; } - return a < b ? -1 : a > b ? 1 : 0; + // ensure a stable sort in V8 and other engines + // http://code.google.com/p/v8/issues/detail?id=90 + return a < b ? -1 : a > b ? 1 : ai < bi ? -1 : 1; } /** - * Used by `template()` to replace tokens with their corresponding code snippets. + * Used by `template` to replace tokens with their corresponding code snippets. * * @private * @param {String} match The matched token. @@ -496,7 +776,7 @@ } /** - * Used by `template()` to escape characters for inclusion in compiled + * Used by `template` to escape characters for inclusion in compiled * string literals. * * @private @@ -508,7 +788,7 @@ } /** - * Used by `escape()` to escape characters for inclusion in HTML. + * Used by `escape` to convert characters to HTML entities. * * @private * @param {String} match The matched character to escape. @@ -543,22 +823,7 @@ } /** - * A shim implementation of `Object.keys` that produces an array of the given - * object's own enumerable property names. - * - * @private - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property names. - */ - var shimKeys = createIterator({ - 'args': 'object', - 'exit': 'if (!objectTypes[typeof object] || object === null) throw TypeError()', - 'init': '[]', - 'inLoop': 'result.push(index)' - }); - - /** - * Used by `template()` to replace "escape" template delimiters with tokens. + * Used by `template` to replace "escape" template delimiters with tokens. * * @private * @param {String} match The matched template delimiter. @@ -566,2424 +831,2858 @@ * @returns {String} Returns a token. */ function tokenizeEscape(match, value) { + if (match && reComplexDelimiter.test(value)) { + return ''; + } var index = tokenized.length; - tokenized[index] = "'+\n_.escape(" + value + ") +\n'"; + tokenized[index] = "' +\n__e(" + value + ") +\n'"; return token + index; } /** - * Used by `template()` to replace "interpolate" template delimiters with tokens. + * Used by `template` to replace "evaluate" template delimiters, or complex + * "escape" and "interpolate" delimiters, with tokens. * * @private * @param {String} match The matched template delimiter. - * @param {String} value The delimiter value. + * @param {String} escapeValue The complex "escape" delimiter value. + * @param {String} interpolateValue The complex "interpolate" delimiter value. + * @param {String} [evaluateValue] The "evaluate" delimiter value. * @returns {String} Returns a token. */ - function tokenizeInterpolate(match, value) { - var index = tokenized.length; - tokenized[index] = "'+\n((__t = (" + value + ")) == null ? '' : __t) +\n'"; - return token + index; + function tokenizeEvaluate(match, escapeValue, interpolateValue, evaluateValue) { + if (evaluateValue) { + var index = tokenized.length; + tokenized[index] = "';\n" + evaluateValue + ";\n__p += '"; + return token + index; + } + return escapeValue + ? tokenizeEscape(null, escapeValue) + : tokenizeInterpolate(null, interpolateValue); } /** - * Used by `template()` to replace "evaluate" template delimiters with tokens. + * Used by `template` to replace "interpolate" template delimiters with tokens. * * @private * @param {String} match The matched template delimiter. * @param {String} value The delimiter value. * @returns {String} Returns a token. */ - function tokenizeEvaluate(match, value) { + function tokenizeInterpolate(match, value) { + if (match && reComplexDelimiter.test(value)) { + return ''; + } var index = tokenized.length; - tokenized[index] = "';\n" + value + ";\n__p += '"; + tokenized[index] = "' +\n((__t = (" + value + ")) == null ? '' : __t) +\n'"; return token + index; } /** - * Converts `collection` to an array of objects by running each element through - * a transformation `callback`. Each object has a `criteria` property containing - * the transformed value to be sorted and a `value` property containing the - * original unmodified value. The `callback` is invoked with 3 arguments; - * for arrays they are (value, index, array) and for objects they are - * (value, key, object). + * Used by `unescape` to convert HTML entities to characters. * * @private - * @param {Array|Object} collection The collection to convert. - * @param {Function} callback The function called per iteration. - * @returns {Array} Returns a new array of objects to sort. + * @param {String} match The matched character to unescape. + * @returns {String} Returns the unescaped character. */ - var toSortable = createIterator(mapIteratorOptions, { - 'args': 'collection, callback', - 'inLoop': { - 'array': - 'result[index] = {\n' + - ' criteria: callback(collection[index], index, collection),\n' + - ' value: collection[index]\n' + - '}', - 'object': - 'result.push({\n' + - ' criteria: callback(collection[index], index, collection),\n' + - ' value: collection[index]\n' + - '})' - } - }); + function unescapeHtmlChar(match) { + return htmlUnescapes[match]; + } /*--------------------------------------------------------------------------*/ /** - * Checks if a given `target` value is present in a `collection` using strict - * equality for comparisons, i.e. `===`. + * Checks if `value` is an `arguments` object. * * @static * @memberOf _ - * @alias include - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Mixed} target The value to check for. - * @returns {Boolean} Returns `true` if `target` value is found, else `false`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is an `arguments` object, else `false`. * @example * - * _.contains([1, 2, 3], 3); + * (function() { return _.isArguments(arguments); })(1, 2, 3); * // => true + * + * _.isArguments([1, 2, 3]); + * // => false */ - var contains = createIterator({ - 'args': 'collection, target', - 'init': 'false', - 'inLoop': 'if (collection[index] === target) return true' - }); + function isArguments(value) { + return toString.call(value) == argsClass; + } + // fallback for browsers that can't detect `arguments` objects by [[Class]] + if (noArgsClass) { + isArguments = function(value) { + return !!(value && hasOwnProperty.call(value, 'callee')); + }; + } /** - * Checks if the `callback` returns a truthy value for **all** elements of a - * `collection`. The `callback` is bound to `thisArg` and invoked with 3 - * arguments; for arrays they are (value, index, array) and for objects they - * are (value, key, object). + * Checks if `value` is an array. * * @static * @memberOf _ - * @alias all - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Boolean} Returns `true` if all values pass the callback check, else `false`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is an array, else `false`. * @example * - * _.every([true, 1, null, 'yes'], Boolean); + * (function() { return _.isArray(arguments); })(); * // => false + * + * _.isArray([1, 2, 3]); + * // => true */ - var every = createIterator(baseIteratorOptions, everyIteratorOptions); + var isArray = nativeIsArray || function(value) { + return toString.call(value) == arrayClass; + }; /** - * Examines each value in a `collection`, returning an array of all values the - * `callback` returns truthy for. The `callback` is bound to `thisArg` and - * invoked with 3 arguments; for arrays they are (value, index, array) and for - * objects they are (value, key, object). + * Checks if `value` is a function. * * @static * @memberOf _ - * @alias select - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of values that passed callback check. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a function, else `false`. * @example * - * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); - * // => [2, 4, 6] + * _.isFunction(''.concat); + * // => true */ - var filter = createIterator(baseIteratorOptions, filterIteratorOptions); + function isFunction(value) { + return typeof value == 'function'; + } + // fallback for older versions of Chrome and Safari + if (isFunction(/x/)) { + isFunction = function(value) { + return toString.call(value) == funcClass; + }; + } /** - * Examines each value in a `collection`, returning the first one the `callback` - * returns truthy for. The function returns as soon as it finds an acceptable - * value, and does not iterate over the entire `collection`. The `callback` is - * bound to `thisArg` and invoked with 3 arguments; for arrays they are - * (value, index, array) and for objects they are (value, key, object). + * 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. * - * @static - * @memberOf _ - * @alias detect - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the value that passed the callback check, else `undefined`. - * @example + * @private + * @param {Mixed} value The value to check. + * @param {Boolean} [skipArgsCheck=false] Internally used to skip checks for + * `arguments` objects. + * @returns {Boolean} Returns `true` if the `value` is a plain `Object` object, + * else `false`. + */ + function isPlainObject(value, skipArgsCheck) { + return value + ? value == ObjectProto || (value.__proto__ == ObjectProto && (skipArgsCheck || !isArguments(value))) + : false; + } + // fallback for IE + if (!isPlainObject(objectTypes)) { + isPlainObject = function(value, skipArgsCheck) { + // avoid non-objects and false positives for `arguments` objects + var result = false; + if (!(value && typeof value == 'object') || (!skipArgsCheck && isArguments(value))) { + return result; + } + // IE < 9 presents DOM nodes as `Object` objects except they have `toString` + // methods that are `typeof` "string" and still can coerce nodes to strings. + // Also check that the constructor is `Object` (i.e. `Object instanceof Object`) + var ctor = value.constructor; + if ((!noNodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) && + (!isFunction(ctor) || ctor instanceof ctor)) { + // IE < 9 iterates inherited properties before own properties. If the first + // iterated property is an object's own property then there are no inherited + // enumerable properties. + if (iteratesOwnLast) { + forIn(value, function(objValue, objKey) { + result = !hasOwnProperty.call(value, objKey); + return false; + }); + return result === 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(objValue, objKey) { + result = objKey; + }); + return result === false || hasOwnProperty.call(value, result); + } + return result; + }; + } + + /** + * A shim implementation of `Object.keys` that produces an array of the given + * object's own enumerable property names. * - * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); - * // => 2 + * @private + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names. */ - var find = createIterator(baseIteratorOptions, forEachIteratorOptions, { - 'init': '', - 'inLoop': 'if (callback(collection[index], index, collection)) return collection[index]' + var shimKeys = createIterator({ + 'args': 'object', + 'init': '[]', + 'inLoop': 'result.push(index)' }); + /*--------------------------------------------------------------------------*/ + /** - * Iterates over a `collection`, executing the `callback` for each value in the - * `collection`. The `callback` is bound to `thisArg` and invoked with 3 - * arguments; for arrays they are (value, index, array) and for objects they - * are (value, key, object). + * Creates a clone of `value`. If `deep` is `true`, all nested objects will + * also be cloned otherwise they will be assigned by reference. If a value has + * a `clone` method it will be used to perform the clone. Functions, DOM nodes, + * `arguments` objects, and objects created by constructors other than `Object` + * are **not** cloned unless they have a custom `clone` method. * * @static * @memberOf _ - * @alias each - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array|Object} Returns the `collection`. + * @category Objects + * @param {Mixed} value The value to clone. + * @param {Boolean} deep A flag to indicate a deep clone. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `deep`. + * @param {Array} [stack=[]] Internally used to keep track of traversed objects + * to avoid circular references. + * @param {Object} thorough Internally used to indicate whether or not to perform + * a more thorough clone of non-object values. + * @returns {Mixed} Returns the cloned `value`. * @example * - * _([1, 2, 3]).forEach(alert).join(','); - * // => alerts each number and returns '1,2,3' + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 }, + * { 'name': 'curly', 'age': 60 } + * ]; * - * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert); - * // => alerts each number (order is not guaranteed) + * _.clone({ 'name': 'moe' }); + * // => { 'name': 'moe' } + * + * var shallow = _.clone(stooges); + * shallow[0] === stooges[0]; + * // => true + * + * var deep = _.clone(stooges, true); + * shallow[0] === stooges[0]; + * // => false */ - var forEach = createIterator(baseIteratorOptions, forEachIteratorOptions); + function clone(value, deep, guard, stack, thorough) { + if (value == null) { + return value; + } + if (guard) { + deep = false; + } + // avoid slower checks on primitives + thorough || (thorough = { 'value': null }); + if (thorough.value == null) { + // primitives passed from iframes use the primary document's native prototypes + thorough.value = !!(BoolProto.clone || NumberProto.clone || StringProto.clone); + } + // use custom `clone` method if available + var isObj = objectTypes[typeof value]; + if ((isObj || thorough.value) && value.clone && isFunction(value.clone)) { + thorough.value = null; + return value.clone(deep); + } + // inspect [[Class]] + if (isObj) { + // don't clone `arguments` objects, functions, or non-object Objects + var className = toString.call(value); + if (!cloneableClasses[className] || (noArgsClass && isArguments(value))) { + return value; + } + var isArr = className == arrayClass; + isObj = isArr || (className == objectClass ? isPlainObject(value, true) : isObj); + } + // shallow clone + if (!isObj || !deep) { + // don't clone functions + return isObj + ? (isArr ? slice.call(value) : extend({}, value)) + : value; + } + + var ctor = value.constructor; + switch (className) { + case boolClass: + return new ctor(value == true); + + case dateClass: + return new ctor(+value); + + case numberClass: + case stringClass: + return new ctor(value); + + case regexpClass: + return ctor(value.source, reFlags.exec(value)); + } + + // check for circular references and return corresponding clone + stack || (stack = []); + var length = stack.length; + while (length--) { + if (stack[length].source == value) { + return stack[length].value; + } + } + + // init cloned object + length = value.length; + var result = isArr ? ctor(length) : {}; + + // add current clone and original source value to the stack of traversed objects + stack.push({ 'value': result, 'source': value }); + + // recursively populate clone (susceptible to call stack limits) + if (isArr) { + var index = -1; + while (++index < length) { + result[index] = clone(value[index], deep, null, stack, thorough); + } + } else { + forOwn(value, function(objValue, key) { + result[key] = clone(objValue, deep, null, stack, thorough); + }); + } + return result; + } /** - * Splits `collection` into sets, grouped by the result of running each value - * through `callback`. The `callback` is bound to `thisArg` and invoked with - * 3 arguments; for arrays they are (value, index, array) and for objects they - * are (value, key, object). The `callback` argument may also be the name of a - * property to group by. + * Assigns enumerable properties of the default object(s) to the `destination` + * object for all `destination` properties that resolve to `null`/`undefined`. + * Once a property is set, additional defaults of the same property will be + * ignored. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function|String} callback The function called per iteration or - * property name to group by. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Object} Returns an object of grouped values. + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [default1, default2, ...] The default objects. + * @returns {Object} Returns the destination object. * @example * - * _.groupBy([1.3, 2.1, 2.4], function(num) { return Math.floor(num); }); - * // => { '1': [1.3], '2': [2.1, 2.4] } - * - * _.groupBy([1.3, 2.1, 2.4], function(num) { return this.floor(num); }, Math); - * // => { '1': [1.3], '2': [2.1, 2.4] } - * - * _.groupBy(['one', 'two', 'three'], 'length'); - * // => { '3': ['one', 'two'], '5': ['three'] } + * var iceCream = { 'flavor': 'chocolate' }; + * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'rainbow' }); + * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' } */ - var groupBy = createIterator(baseIteratorOptions, { - 'init': '{}', - 'top': - 'var prop, isFunc = typeof callback == \'function\';\n' + - 'if (isFunc && thisArg) callback = iteratorBind(callback, thisArg)', - 'inLoop': - 'prop = isFunc\n' + - ' ? callback(collection[index], index, collection)\n' + - ' : collection[index][callback];\n' + - '(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(collection[index])' + var defaults = createIterator(extendIteratorOptions, { + 'inLoop': 'if (result[index] == null) ' + extendIteratorOptions.inLoop }); /** - * Invokes the method named by `methodName` on each element in the `collection`. - * 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`. + * 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 `callback` is passed, it will be executed for each property + * in the `object`, dropping the properties `callback` returns truthy for. The + * `callback` is bound to `thisArg` and invoked with 3 arguments; (value, key, object). * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} 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 values returned from each invoked method. + * @alias omit + * @category Objects + * @param {Object} object The source object. + * @param {Function|String} callback|[prop1, prop2, ...] The properties to drop + * or the function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Object} Returns an object without the dropped properties. * @example * - * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); - * // => [[1, 5, 7], [1, 2, 3]] + * _.drop({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'userid'); + * // => { 'name': 'moe', 'age': 40 } * - * _.invoke([123, 456], String.prototype.split, ''); - * // => [['1', '2', '3'], ['4', '5', '6']] + * _.drop({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) { + * return key.charAt(0) == '_'; + * }); + * // => { 'name': 'moe' } */ - var invoke = createIterator(mapIteratorOptions, { - 'args': 'collection, methodName', - 'top': - 'var args = slice.call(arguments, 2),\n' + - ' isFunc = typeof methodName == \'function\'', - 'inLoop': { - 'array': 'result[index] = (isFunc ? methodName : collection[index][methodName]).apply(collection[index], args)', - 'object': 'result.push((isFunc ? methodName : collection[index][methodName]).apply(collection[index], args))' - } - }); + var drop = createIterator(dropIteratorOptions); /** - * Produces a new array of values by mapping each element in the `collection` - * through a transformation `callback`. The `callback` is bound to `thisArg` - * and invoked with 3 arguments; for arrays they are (value, index, array) - * and for objects they are (value, key, object). + * Assigns enumerable properties of the source object(s) to the `destination` + * object. Subsequent sources will overwrite propery assignments of previous + * sources. * * @static * @memberOf _ - * @alias collect - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of values returned by the callback. + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @returns {Object} Returns the destination object. * @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) + * _.extend({ 'name': 'moe' }, { 'age': 40 }); + * // => { 'name': 'moe', 'age': 40 } */ - var map = createIterator(baseIteratorOptions, mapIteratorOptions); + var extend = createIterator(extendIteratorOptions); /** - * Retrieves the value of a specified property from all elements in - * the `collection`. + * Iterates over `object`'s own and inherited enumerable properties, executing + * the `callback` for each property. The `callback` is bound to `thisArg` and + * invoked with 3 arguments; (value, key, object). Callbacks may exit iteration + * early by explicitly returning `false`. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {String} property The property to pluck. - * @returns {Array} Returns a new array of property values. + * @category Objects + * @param {Object} object The object to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Object} Returns `object`. * @example * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 }, - * { 'name': 'curly', 'age': 60 } - * ]; + * function Dog(name) { + * this.name = name; + * } * - * _.pluck(stooges, 'name'); - * // => ['moe', 'larry', 'curly'] + * 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 pluck = createIterator(mapIteratorOptions, { - 'args': 'collection, property', - 'inLoop': { - 'array': 'result[index] = collection[index][property]', - 'object': 'result.push(collection[index][property])' - } + var forIn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions, { + 'useHas': false }); /** - * Boils down a `collection` to a single value. The initial state of the - * reduction is `accumulator` and each successive step of it should be returned - * by the `callback`. The `callback` is bound to `thisArg` and invoked with 4 - * arguments; for arrays they are (accumulator, value, index, array) and for - * objects they are (accumulator, value, key, object). + * Iterates over `object`'s own enumerable properties, executing the `callback` + * for each property. The `callback` is bound to `thisArg` and invoked with 3 + * arguments; (value, key, object). Callbacks may exit iteration early by + * explicitly returning `false`. * * @static * @memberOf _ - * @alias foldl, inject - * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @category Objects + * @param {Object} object The object to iterate over. * @param {Function} callback The function called per iteration. - * @param {Mixed} [accumulator] Initial value of the accumulator. * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the accumulated value. + * @returns {Object} Returns `object`. * @example * - * var sum = _.reduce([1, 2, 3], function(memo, num) { return memo + num; }); - * // => 6 + * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { + * alert(key); + * }); + * // => alerts '0', '1', and 'length' (order is not guaranteed) */ - var reduce = createIterator({ - 'args': 'collection, callback, accumulator, thisArg', - 'init': 'accumulator', - 'top': - 'var noaccum = arguments.length < 3;\n' + - 'if (thisArg) callback = iteratorBind(callback, thisArg)', - 'beforeLoop': { - 'array': 'if (noaccum) result = collection[++index]' - }, - 'inLoop': { - 'array': - 'result = callback(result, collection[index], index, collection)', - 'object': - 'result = noaccum\n' + - ' ? (noaccum = false, collection[index])\n' + - ' : callback(result, collection[index], index, collection)' - } - }); + var forOwn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions); /** - * The right-associative version of `_.reduce`. + * Creates a sorted array of all enumerable properties, own and inherited, + * of `object` that have function values. * * @static * @memberOf _ - * @alias foldr - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} [accumulator] Initial value of the accumulator. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the accumulated value. + * @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 * - * 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] + * _.functions(_); + * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] */ - function reduceRight(collection, callback, accumulator, thisArg) { - if (!collection) { - return accumulator; - } - - var length = collection.length, - noaccum = arguments.length < 3; - - if(thisArg) { - callback = iteratorBind(callback, thisArg); - } - if (length === length >>> 0) { - if (length && noaccum) { - accumulator = collection[--length]; - } - while (length--) { - accumulator = callback(accumulator, collection[length], length, collection); - } - return accumulator; - } - - var prop, - props = keys(collection); - - length = props.length; - if (length && noaccum) { - accumulator = collection[props[--length]]; - } - while (length--) { - prop = props[length]; - accumulator = callback(accumulator, collection[prop], prop, collection); - } - return accumulator; - } - - /** - * The opposite of `_.filter`, this method returns the values of a `collection` - * that `callback` does **not** return truthy for. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of values 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 reject = createIterator(baseIteratorOptions, filterIteratorOptions, { - 'inLoop': '!' + filterIteratorOptions.inLoop - }); + var functions = createIterator({ + 'useHas': false, + 'args': 'object', + 'init': '[]', + 'inLoop': 'if (isFunction(value)) result.push(index)', + 'bottom': 'result.sort()' + }); /** - * 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 3 arguments; for arrays they are - * (value, index, array) and for objects they are (value, key, object). + * Checks if the specified object `property` exists and is a direct property, + * instead of an inherited property. * * @static * @memberOf _ - * @alias any - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Boolean} Returns `true` if any value passes the callback check, else `false`. + * @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 * - * _.some([null, 0, 'yes', false]); + * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); * // => true */ - var some = createIterator(baseIteratorOptions, everyIteratorOptions, { - 'init': 'false', - 'inLoop': everyIteratorOptions.inLoop.replace('!', '') - }); - + function has(object, property) { + return object ? hasOwnProperty.call(object, property) : false; + } /** - * Produces a new sorted array, ranked in ascending order by the results of - * running each element of `collection` through a transformation `callback`. - * The `callback` is bound to `thisArg` and invoked with 3 arguments; - * for arrays they are (value, index, array) and for objects they are - * (value, key, object). The `callback` argument may also be the name of a - * property to sort by (e.g. 'length'). + * Checks if `value` is a boolean (`true` or `false`) value. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function|String} callback The function called per iteration or - * property name to sort by. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of sorted values. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a boolean value, else `false`. * @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] - * - * _.sortBy(['larry', 'brendan', 'moe'], 'length'); - * // => ['moe', 'larry', 'brendan'] + * _.isBoolean(null); + * // => false */ - function sortBy(collection, callback, thisArg) { - if (typeof callback == 'string') { - var prop = callback; - callback = function(collection) { return collection[prop]; }; - } else if (thisArg) { - callback = iteratorBind(callback, thisArg); - } - var result = toSortable(collection, callback).sort(compareAscending), - length = result.length; - - while (length--) { - result[length] = result[length].value; - } - return result; + function isBoolean(value) { + return value === true || value === false || toString.call(value) == boolClass; } /** - * Converts the `collection`, into an array. Useful for converting the - * `arguments` object. + * Checks if `value` is a date. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to convert. - * @returns {Array} Returns the new converted array. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a date, else `false`. * @example * - * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4); - * // => [2, 3, 4] + * _.isDate(new Date); + * // => true */ - function toArray(collection) { - if (!collection) { - return []; - } - if (collection.toArray && toString.call(collection.toArray) == funcClass) { - return collection.toArray(); - } - var length = collection.length; - if (length === length >>> 0) { - return slice.call(collection); - } - return values(collection); + function isDate(value) { + return toString.call(value) == dateClass; } - /*--------------------------------------------------------------------------*/ - /** - * Produces a new array with all falsey values of `array` removed. The values - * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey. + * Checks if `value` is a DOM element. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to compact. - * @returns {Array} Returns a new filtered array. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a DOM element, else `false`. * @example * - * _.compact([0, 1, false, 2, '', 3]); - * // => [1, 2, 3] + * _.isElement(document.body); + * // => true */ - function compact(array) { - var result = []; - if (!array) { - return result; - } - var index = -1, - length = array.length; - - while (++index < length) { - if (array[index]) { - result.push(array[index]); - } - } - return result; + function isElement(value) { + return value ? value.nodeType === 1 : false; } /** - * Produces a new array of `array` values not present in the other arrays - * using strict equality for comparisons, i.e. `===`. + * 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 Arrays - * @param {Array} array The array to process. - * @param {Array} [array1, array2, ...] Arrays to check. - * @returns {Array} Returns a new array of `array` values not present in the - * other arrays. + * @category Objects + * @param {Array|Object|String} value The value to inspect. + * @returns {Boolean} Returns `true` if the `value` is empty, else `false`. * @example * - * _.difference([1, 2, 3, 4, 5], [5, 2, 10]); - * // => [1, 3, 4] - */ - function difference(array) { - var result = []; - if (!array) { - return result; - } - var index = -1, - length = array.length, - flattened = concat.apply(result, arguments); - - while (++index < length) { - if (indexOf(flattened, array[index], length) < 0) { - result.push(array[index]); - } - } - return result; - } - - /** - * Gets the first value of the `array`. Pass `n` to return the first `n` values - * of the `array`. + * _.isEmpty([1, 2, 3]); + * // => false * - * @static - * @memberOf _ - * @alias head, take - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Mixed} Returns the first value or an array of the first `n` values - * of `array`. - * @example + * _.isEmpty({}); + * // => true * - * _.first([5, 4, 3, 2, 1]); - * // => 5 + * _.isEmpty(''); + * // => true */ - function first(array, n, guard) { - if (array) { - return (n == null || guard) ? array[0] : slice.call(array, 0, n); + var isEmpty = createIterator({ + 'args': 'value', + 'init': 'true', + 'top': + 'var className = toString.call(value),\n' + + ' length = value.length;\n' + + 'if (arrayLikeClasses[className]' + + (noArgsClass ? ' || isArguments(value)' : '') + ' ||\n' + + ' (className == objectClass && length > -1 && length === length >>> 0 &&\n' + + ' isFunction(value.splice))' + + ') return !length', + 'inLoop': { + 'object': 'return false' } - } + }); /** - * Flattens a nested array (the nesting can be to any depth). If `shallow` is - * truthy, `array` will only be flattened a single level. + * Performs a deep comparison between two values to determine if they are + * equivalent to each other. If a value has an `isEqual` method it will be + * used to perform the comparison. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to compact. - * @param {Boolean} shallow A flag to indicate only flattening a single level. - * @returns {Array} Returns a new flattened array. + * @category Objects + * @param {Mixed} a The value to compare. + * @param {Mixed} b The other value to compare. + * @param {Array} [stack=[]] Internally used to keep track of traversed objects + * to avoid circular references. + * @param {Object} thorough Internally used to indicate whether or not to perform + * a more thorough comparison of non-object values. + * @returns {Boolean} Returns `true` if the values are equvalent, else `false`. * @example * - * _.flatten([1, [2], [3, [[4]]]]); - * // => [1, 2, 3, 4]; + * var moe = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; + * var clone = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; * - * _.flatten([1, [2], [3, [[4]]]], true); - * // => [1, 2, 3, [[4]]]; + * moe == clone; + * // => false + * + * _.isEqual(moe, clone); + * // => true */ - function flatten(array, shallow) { - var result = []; - if (!array) { - return result; + function isEqual(a, b, stack, thorough) { + // a strict comparison is necessary because `null == undefined` + if (a == null || b == null) { + return a === b; } - var value, - index = -1, - length = array.length; - - while (++index < length) { - value = array[index]; - if (isArray(value)) { - push.apply(result, shallow ? value : flatten(value)); - } else { - result.push(value); + // avoid slower checks on non-objects + thorough || (thorough = { 'value': null }); + if (thorough.value == null) { + // primitives passed from iframes use the primary document's native prototypes + thorough.value = !!(BoolProto.isEqual || NumberProto.isEqual || StringProto.isEqual); + } + if (objectTypes[typeof a] || objectTypes[typeof b] || thorough.value) { + // unwrap any LoDash wrapped values + if (a._chain) { + a = a._wrapped; + } + if (b._chain) { + b = b._wrapped; + } + // use custom `isEqual` method if available + if (a.isEqual && isFunction(a.isEqual)) { + thorough.value = null; + return a.isEqual(b); + } + if (b.isEqual && isFunction(b.isEqual)) { + thorough.value = null; + return b.isEqual(a); } } - return result; - } + // exit early for identical values + if (a === b) { + // treat `+0` vs. `-0` as not equal + return a !== 0 || (1 / a == 1 / b); + } + // compare [[Class]] names + var className = toString.call(a); + if (className != toString.call(b)) { + 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; - /** - * 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 `isSorted` 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 start searching 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 (!array) { - return -1; + 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 == b + ''; } + // exit early, in older browsers, if `a` is array-like but not `b` + var isArr = arrayLikeClasses[className]; + if (noArgsClass && !isArr && (isArr = isArguments(a)) && !isArguments(b)) { + return false; + } + // exit for functions and DOM nodes + if (!isArr && (className != objectClass || (noNodeClass && ( + (typeof a.toString != 'function' && typeof (a + '') == 'string') || + (typeof b.toString != 'function' && typeof (b + '') == 'string'))))) { + 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) + stack || (stack = []); + var length = stack.length; + while (length--) { + if (stack[length] == a) { + return true; + } + } + var index = -1, - length = array.length; + result = true, + size = 0; - if (fromIndex) { - if (typeof fromIndex == 'number') { - index = (fromIndex < 0 ? Math.max(0, length + fromIndex) : fromIndex) - 1; - } else { - index = sortedIndex(array, value); - return array[index] === value ? index : -1; + // add `a` to the stack of traversed objects + stack.push(a); + + // recursively compare objects and arrays (susceptible to call stack limits) + if (isArr) { + // compare lengths to determine if a deep comparison is necessary + size = a.length; + result = size == b.length; + + if (result) { + // deep compare the contents, ignoring non-numeric properties + while (size--) { + if (!(result = isEqual(a[size], b[size], stack, thorough))) { + break; + } + } } + return result; } - while (++index < length) { - if (array[index] === value) { - return index; + + 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; + } + // deep compare objects + for (var prop in a) { + if (hasOwnProperty.call(a, prop)) { + // count the number of properties. + size++; + // deep compare each property value. + if (!(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack, thorough))) { + return false; + } } } - return -1; + // ensure both objects have the same number of properties + for (prop in b) { + // The JS engine in Adobe products, like InDesign, has a bug that causes + // `!size--` to throw an error so it must be wrapped in parentheses. + // https://github.com/documentcloud/underscore/issues/355 + if (hasOwnProperty.call(b, prop) && !(size--)) { + // `size` will be `-1` if `b` has more properties than `a` + return false; + } + } + // handle JScript [[DontEnum]] bug + if (hasDontEnumBug) { + while (++index < 7) { + prop = shadowed[index]; + if (hasOwnProperty.call(a, prop) && + !(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack, thorough))) { + return false; + } + } + } + return true; } /** - * Gets all but the last value of `array`. Pass `n` to exclude the last `n` - * values from the result. + * Checks if `value` is a finite number. + * + * Note: This is not the same as native `isFinite`, which will return true for + * booleans and other values. See http://es5.github.com/#x15.1.2.5. * + * @deprecated * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Array} Returns all but the last value or `n` values of `array`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a finite number, else `false`. * @example * - * _.initial([3, 2, 1]); - * // => [3, 2] + * _.isFinite(-101); + * // => true + * + * _.isFinite('10'); + * // => false + * + * _.isFinite(Infinity); + * // => false */ - function initial(array, n, guard) { - if (!array) { - return []; - } - return slice.call(array, 0, -((n == null || guard) ? 1 : n)); + function isFinite(value) { + return nativeIsFinite(value) && toString.call(value) == numberClass; } /** - * Computes the intersection of all the passed-in arrays. + * Checks if `value` is the language type of Object. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @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 **all** of the arrays. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is an object, else `false`. * @example * - * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); - * // => [1, 2] + * _.isObject({}); + * // => true + * + * _.isObject(1); + * // => false */ - function intersection(array) { - var result = []; - if (!array) { - return result; - } - var value, - index = -1, - length = array.length, - others = slice.call(arguments, 1); - - while (++index < length) { - value = array[index]; - if (indexOf(result, value) < 0 && - every(others, function(other) { return indexOf(other, value) > -1; })) { - result.push(value); - } - } - return result; + 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] : false; } /** - * Gets the last value of the `array`. Pass `n` to return the lasy `n` values - * of the `array`. + * 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. * + * @deprecated * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Mixed} Returns the last value or an array of the last `n` values - * of `array`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is `NaN`, else `false`. * @example * - * _.last([3, 2, 1]); - * // => 1 + * _.isNaN(NaN); + * // => true + * + * _.isNaN(new Number(NaN)); + * // => true + * + * isNaN(undefined); + * // => true + * + * _.isNaN(undefined); + * // => false */ - function last(array, n, guard) { - if (array) { - var length = array.length; - return (n == null || guard) ? array[length - 1] : slice.call(array, -n || length); - } + 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 toString.call(value) == numberClass && value != +value } /** - * Gets the index at which the last occurrence of `value` is found using - * strict equality for comparisons, i.e. `===`. + * Checks if `value` is `null`. * + * @deprecated * @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 start searching from. - * @returns {Number} Returns the index of the matched value or `-1`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is `null`, else `false`. * @example * - * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); - * // => 4 + * _.isNull(null); + * // => true * - * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3); - * // => 1 + * _.isNull(undefined); + * // => false */ - function lastIndexOf(array, value, fromIndex) { - if (!array) { - return -1; - } - var index = array.length; - if (fromIndex && typeof fromIndex == 'number') { - index = (fromIndex < 0 ? Math.max(0, index + fromIndex) : Math.min(fromIndex, index - 1)) + 1; - } - while (index--) { - if (array[index] === value) { - return index; - } - } - return -1; + function isNull(value) { + return value === null; } /** - * 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 3 arguments; (value, index, array). + * Checks if `value` is a number. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to iterate over. - * @param {Function} [callback] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the maximum value. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a number, else `false`. * @example * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 }, - * { 'name': 'curly', 'age': 60 } - * ]; - * - * _.max(stooges, function(stooge) { return stooge.age; }); - * // => { 'name': 'curly', 'age': 60 }; + * _.isNumber(8.4 * 5; + * // => true */ - function max(array, callback, thisArg) { - var computed = -Infinity, - result = computed; - - if (!array) { - return result; - } - var current, - index = -1, - length = array.length; - - if (!callback) { - while (++index < length) { - if (array[index] > result) { - result = array[index]; - } - } - return result; - } - if (thisArg) { - callback = iteratorBind(callback, thisArg); - } - while (++index < length) { - current = callback(array[index], index, array); - if (current > computed) { - computed = current; - result = array[index]; - } - } - return result; - } + function isNumber(value) { + return toString.call(value) == numberClass; + } /** - * 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 3 arguments; (value, index, array). + * Checks if `value` is a regular expression. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to iterate over. - * @param {Function} [callback] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the minimum value. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a regular expression, else `false`. * @example * - * _.min([10, 5, 100, 2, 1000]); - * // => 2 + * _.isRegExp(/moe/); + * // => true */ - function min(array, callback, thisArg) { - var computed = Infinity, - result = computed; - - if (!array) { - return result; - } - var current, - index = -1, - length = array.length; - - if (!callback) { - while (++index < length) { - if (array[index] < result) { - result = array[index]; - } - } - return result; - } - if (thisArg) { - callback = iteratorBind(callback, thisArg); - } - while (++index < length) { - current = callback(array[index], index, array); - if (current < computed) { - computed = current; - result = array[index]; - } - } - return result; + function isRegExp(value) { + return toString.call(value) == regexpClass; } /** - * Creates an array of numbers (positive and/or negative) progressing from - * `start` up to but not including `stop`. This method is a port of Python's - * `range()` function. See http://docs.python.org/library/functions.html#range. + * Checks if `value` is a string. * * @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 descrement by. - * @returns {Array} Returns a new range array. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a string, else `false`. * @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); - * // => [] + * _.isString('moe'); + * // => true */ - function range(start, end, step) { - step || (step = 1); - if (end == null) { - end = start || 0; - start = 0; - } - // use `Array(length)` so V8 will avoid the slower "dictionary" mode - // http://www.youtube.com/watch?v=XAqIpGU8ZZk#t=16m27s - var index = -1, - length = Math.max(0, Math.ceil((end - start) / step)), - result = Array(length); - - while (++index < length) { - result[index] = start; - start += step; - } - return result; + function isString(value) { + return toString.call(value) == stringClass; } /** - * The opposite of `_.initial`, this method gets all but the first value of - * `array`. Pass `n` to exclude the first `n` values from the result. + * Checks if `value` is `undefined`. * + * @deprecated * @static * @memberOf _ - * @alias tail - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Array} Returns all but the first value or `n` values of `array`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is `undefined`, else `false`. * @example * - * _.rest([3, 2, 1]); - * // => [2, 1] + * _.isUndefined(void 0); + * // => true */ - function rest(array, n, guard) { - if (!array) { - return []; - } - return slice.call(array, (n == null || guard) ? 1 : n); + function isUndefined(value) { + return value === undefined; } /** - * Produces a new array of shuffled `array` values, using a version of the - * Fisher-Yates shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. + * Creates an array composed of the own enumerable property names of `object`. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to shuffle. - * @returns {Array} Returns a new shuffled array. + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names. * @example * - * _.shuffle([1, 2, 3, 4, 5, 6]); - * // => [4, 1, 6, 3, 5, 2] + * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); + * // => ['one', 'two', 'three'] (order is not guaranteed) */ - function shuffle(array) { - if (!array) { - return []; - } - var rand, - index = -1, - length = array.length, - result = Array(length); + var keys = !nativeKeys ? shimKeys : function(object) { + var type = typeof object; - while (++index < length) { - rand = Math.floor(Math.random() * (index + 1)); - result[index] = result[rand]; - result[rand] = array[index]; + // avoid iterating over the `prototype` property + if (type == 'function' && propertyIsEnumerable.call(object, 'prototype')) { + return shimKeys(object); } - return result; - } + return object && objectTypes[type] + ? nativeKeys(object) + : []; + }; /** - * 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 1 argument; (value). + * Merges enumerable properties of the source object(s) into the `destination` + * object. Subsequent sources will overwrite propery assignments of previous + * sources. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to iterate over. - * @param {Mixed} value The value to evaluate. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Number} Returns the index at which the value should be inserted - * into `array`. + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @param {Object} [indicator] Internally used to indicate that the `stack` + * argument is an array of traversed objects instead of another source object. + * @param {Array} [stack=[]] Internally used to keep track of traversed objects + * to avoid circular references. + * @returns {Object} Returns the destination object. * @example * - * _.sortedIndex([20, 30, 40], 35); - * // => 2 - * - * var dict = { - * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'thirty-five': 35, 'fourty': 40 } - * }; + * var stooges = [ + * { 'name': 'moe' }, + * { 'name': 'larry' } + * ]; * - * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { - * return dict.wordToNumber[word]; - * }); - * // => 2 + * var ages = [ + * { 'age': 40 }, + * { 'age': 50 } + * ]; * - * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { - * return this.wordToNumber[word]; - * }, dict); - * // => 2 + * _.merge(stooges, ages); + * // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] */ - function sortedIndex(array, value, callback, thisArg) { - if (!array) { - return 0; - } - var mid, - low = 0, - high = array.length; - - if (callback) { - if (thisArg) { - callback = bind(callback, thisArg); - } - value = callback(value); - while (low < high) { - mid = (low + high) >>> 1; - callback(array[mid]) < value ? low = mid + 1 : high = mid; - } - } else { - while (low < high) { - mid = (low + high) >>> 1; - array[mid] < value ? low = mid + 1 : high = mid; - } - } - return low; - } + var merge = createIterator(extendIteratorOptions, { + 'args': 'object, source, indicator, stack', + 'top': + 'var destValue, found, isArr, stackLength, recursive = indicator == isPlainObject;\n' + + 'if (!recursive) stack = [];\n' + + 'for (var argsIndex = 1, argsLength = recursive ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n' + + ' if (iteratee = arguments[argsIndex]) {', + 'inLoop': + 'if (value && ((isArr = isArray(value)) || isPlainObject(value))) {\n' + + ' found = false; stackLength = stack.length;\n' + + ' while (stackLength--) {\n' + + ' if (found = stack[stackLength].source == value) break\n' + + ' }\n' + + ' if (found) {\n' + + ' result[index] = stack[stackLength].value\n' + + ' } else {\n' + + ' destValue = (destValue = result[index]) && isArr\n' + + ' ? (isArray(destValue) ? destValue : [])\n' + + ' : (isPlainObject(destValue) ? destValue : {});\n' + + ' stack.push({ value: destValue, source: value });\n' + + ' result[index] = callee(destValue, value, isPlainObject, stack)\n' + + ' }\n' + + '} else if (value != null) {\n' + + ' result[index] = value\n' + + '}' + }); /** - * Computes the union of the passed-in arrays. + * 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 3 arguments; (value, key, object). * * @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. + * @category Objects + * @param {Object} object The source object. + * @param {Function|String} callback|[prop1, prop2, ...] The properties to pick + * or the function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Object} Returns an object composed of the picked properties. * @example * - * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); - * // => [1, 2, 3, 101, 10] + * _.pick({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'name', 'age'); + * // => { 'name': 'moe', 'age': 40 } + * + * _.pick({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) { + * return key.charAt(0) != '_'; + * }); + * // => { 'name': 'moe' } */ - function union() { - var index = -1, - result = [], - flattened = concat.apply(result, arguments), - length = flattened.length; - - while (++index < length) { - if (indexOf(result, flattened[index]) < 0) { - result.push(flattened[index]); - } - } - return result; - } + var pick = createIterator(dropIteratorOptions, { + 'top': + 'if (typeof callback != \'function\') {\n' + + ' var prop,\n' + + ' props = concat.apply(ArrayProto, arguments),\n' + + ' length = props.length;\n' + + ' for (index = 1; index < length; index++) {\n' + + ' prop = props[index];\n' + + ' if (prop in object) result[prop] = object[prop]\n' + + ' }\n' + + '} else {\n' + + ' if (thisArg) callback = iteratorBind(callback, thisArg)', + 'inLoop': + 'if (callback(value, index, object)) result[index] = value', + 'bottom': '}' + }); /** - * Produces 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 value of `array` is passed through a transformation `callback` before - * uniqueness is computed. The `callback` is bound to `thisArg` and invoked - * with 3 arguments; (value, index, array). + * Gets the size of `value` by returning `value.length` if `value` is an + * array, string, or `arguments` object. If `value` is an object, size is + * determined by returning the number of own enumerable properties it has. * * @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} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a duplicate-value-free array. + * @category Objects + * @param {Array|Object|String} value The value to inspect. + * @returns {Number} Returns `value.length` or number of own enumerable properties. * @example * - * _.uniq([1, 2, 1, 3, 1]); - * // => [1, 2, 3] - * - * _.uniq([1, 1, 2, 2, 3], true); - * // => [1, 2, 3] + * _.size([1, 2]); + * // => 2 * - * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); }); - * // => [1, 2, 3] + * _.size({ 'one': 1, 'two': 2, 'three': 3 }); + * // => 3 * - * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math); - * // => [1, 2, 3] + * _.size('curly'); + * // => 5 */ - function uniq(array, isSorted, callback, thisArg) { - var result = []; - if (!array) { - return result; - } - var computed, - index = -1, - length = array.length, - seen = []; - - // juggle arguments - if (typeof isSorted == 'function') { - thisArg = callback; - callback = isSorted; - isSorted = false; - } - if (!callback) { - callback = identity; - } else if (thisArg) { - callback = iteratorBind(callback, thisArg); + function size(value) { + if (!value) { + return 0; } - while (++index < length) { - computed = callback(array[index], index, array); - if (isSorted - ? !index || seen[seen.length - 1] !== computed - : indexOf(seen, computed) < 0 - ) { - seen.push(computed); - result.push(array[index]); - } + var className = toString.call(value), + length = value.length; + + // return `value.length` for `arguments` objects, arrays, strings, and DOM + // query collections of libraries like jQuery and MooTools + // http://code.google.com/p/fbug/source/browse/branches/firebug1.9/content/firebug/chrome/reps.js?r=12614#653 + // http://trac.webkit.org/browser/trunk/Source/WebCore/inspector/InjectedScriptSource.js?rev=125186#L609 + if (arrayLikeClasses[className] || (noArgsClass && isArguments(value)) || + (className == objectClass && length > -1 && length === length >>> 0 && isFunction(value.splice))) { + return length; } - return result; + return keys(value).length; } /** - * Produces a new array with all occurrences of the passed values removed using - * strict equality for comparisons, i.e. `===`. + * Creates an array composed of the own enumerable property values of `object`. * * @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. + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property values. * @example * - * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); - * // => [2, 3, 4] + * _.values({ 'one': 1, 'two': 2, 'three': 3 }); + * // => [1, 2, 3] */ - function without(array) { - var result = []; - if (!array) { - return result; - } - var index = -1, - length = array.length; + var values = createIterator({ + 'args': 'object', + 'init': '[]', + 'inLoop': 'result.push(value)' + }); - while (++index < length) { - if (indexOf(arguments, array[index], 1) < 0) { - result.push(array[index]); - } - } - return result; - } + /*--------------------------------------------------------------------------*/ /** - * Merges together the values of each of the arrays with the value at the - * corresponding position. 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. + * Checks if a given `target` element is present in a `collection` 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 merged arrays. + * @alias include + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Mixed} target The value to check for. + * @returns {Boolean} Returns `true` if the `target` element is found, else `false`. * @example * - * _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]); - * // => [['moe', 30, true], ['larry', 40, false], ['curly', 50, false]] + * _.contains([1, 2, 3], 3); + * // => true + * + * _.contains({ 'name': 'moe', 'age': 40 }, 'moe'); + * // => true + * + * _.contains('curly', 'ur'); + * // => true */ - function zip(array) { - if (!array) { - return []; - } - var index = -1, - length = max(pluck(arguments, 'length')), - result = Array(length); - - while (++index < length) { - result[index] = pluck(arguments, index); - } - return result; - } - - /*--------------------------------------------------------------------------*/ + var contains = createIterator({ + 'args': 'collection, target', + 'init': 'false', + 'noCharByIndex': false, + 'beforeLoop': { + 'array': 'if (toString.call(collection) == stringClass) return collection.indexOf(target) > -1' + }, + 'inLoop': 'if (value === target) return true' + }); /** - * Creates a new function that is restricted to executing only after it is - * called `n` times. + * Creates an object composed of keys returned from running each element of + * `collection` through a `callback`. The corresponding value of each key is + * the number of times the key was returned by `callback`. The `callback` is + * bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection). + * The `callback` argument may also be the name of a property to count by (e.g. 'length'). * * @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. + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|String} callback|property The function called per iteration + * or property name to count by. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Object} Returns the composed aggregate object. * @example * - * var renderNotes = _.after(notes.length, render); - * _.forEach(notes, function(note) { - * note.asyncSave({ 'success': renderNotes }); - * }); - * // `renderNotes` is run once, after all notes have saved + * _.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 after(n, func) { - if (n < 1) { - return func(); - } - return function() { - if (--n < 1) { - return func.apply(this, arguments); - } - }; - } + var countBy = createIterator(baseIteratorOptions, countByIteratorOptions); /** - * Creates a new 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. Lazy defined methods may be bound by passing - * the object they are bound to as `func` and the method name as `thisArg`. + * Checks if the `callback` returns a truthy value for **all** elements of a + * `collection`. The `callback` is bound to `thisArg` and invoked with 3 + * arguments; (value, index|key, collection). * * @static * @memberOf _ - * @category Functions - * @param {Function|Object} func The function to bind or the object the method belongs to. - * @param {Mixed} [thisArg] The `this` binding of `func` or the method name. - * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. - * @returns {Function} Returns the new bound function. + * @alias all + * @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 for the callback. + * @returns {Boolean} Returns `true` if all elements pass the callback check, else `false`. * @example * - * // basic bind - * var func = function(greeting) { - * return greeting + ': ' + this.name; - * }; - * - * func = _.bind(func, { 'name': 'moe' }, 'hi'); - * func(); - * // => 'hi: moe' - * - * // lazy bind - * var object = { - * 'name': 'moe', - * 'greet': function(greeting) { - * return greeting + ': ' + this.name; - * } - * }; - * - * var func = _.bind(object, 'greet', 'hi'); - * func(); - * // => 'hi: moe' - * - * object.greet = function(greeting) { - * return greeting + ', ' + this.name + '!'; - * }; - * - * func(); - * // => 'hi, moe!' + * _.every([true, 1, null, 'yes'], Boolean); + * // => false */ - function bind(func, thisArg) { - var methodName, - isFunc = toString.call(func) == funcClass; - - // juggle arguments - if (!isFunc) { - methodName = thisArg; - thisArg = func; - } - // use if `Function#bind` is faster - else if (nativeBind) { - return nativeBind.call.apply(nativeBind, arguments); - } - - var partialArgs = slice.call(arguments, 2); - - function bound() { - // `Function#bind` spec - // http://es5.github.com/#x15.3.4.5 - var args = arguments, - thisBinding = thisArg; - - if (!isFunc) { - func = thisArg[methodName]; - } - if (partialArgs.length) { - args = args.length - ? concat.apply(partialArgs, args) - : partialArgs; - } - if (this instanceof bound) { - // get `func` instance if `bound` is invoked in a `new` expression - noop.prototype = func.prototype; - thisBinding = new noop; - - // mimic the constructor's `return` behavior - // http://es5.github.com/#x13.2.2 - var result = func.apply(thisBinding, args); - return objectTypes[typeof result] && result !== null - ? result - : thisBinding - } - return func.apply(thisBinding, args); - } - - return bound; - } + var every = createIterator(baseIteratorOptions, everyIteratorOptions); /** - * Binds methods on `object` to `object`, overwriting the existing method. - * If no method names are provided, all the function properties of `object` - * will be bound. + * 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 3 arguments; (value, index|key, collection). * * @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 the `object`. + * @alias select + * @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 for the callback. + * @returns {Array} Returns a new array of elements that passed callback check. * @example * - * var buttonView = { - * 'label': 'lodash', - * 'onClick': function() { alert('clicked: ' + this.label); } - * }; - * - * _.bindAll(buttonView); - * jQuery('#lodash_button').on('click', buttonView.onClick); - * // => When the button is clicked, `this.label` will have the correct value + * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [2, 4, 6] */ - function bindAll(object) { - var funcs = arguments, - index = 1; - - if (funcs.length == 1) { - index = 0; - funcs = functions(object); - } - for (var length = funcs.length; index < length; index++) { - object[funcs[index]] = bind(object[funcs[index]], object); - } - return object; - } + var filter = createIterator(baseIteratorOptions, filterIteratorOptions); /** - * Creates a new function that is the composition of the passed functions, - * where each function consumes the return value of the function that follows. - * In math terms, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. + * Examines each element in a `collection`, returning the first one the `callback` + * returns truthy for. The function returns as soon as it finds an acceptable + * element, and does not iterate over the entire `collection`. The `callback` is + * bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection). * * @static * @memberOf _ - * @category Functions - * @param {Function} [func1, func2, ...] Functions to compose. - * @returns {Function} Returns the new composed function. + * @alias detect + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the element that passed the callback check, else `undefined`. * @example * - * var greet = function(name) { return 'hi: ' + name; }; - * var exclaim = function(statement) { return statement + '!'; }; - * var welcome = _.compose(exclaim, greet); - * welcome('moe'); - * // => 'hi: moe!' + * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => 2 */ - function compose() { - var funcs = arguments; - return function() { - var args = arguments, - length = funcs.length; - - while (length--) { - args = [funcs[length].apply(this, args)]; - } - return args[0]; - }; - } + var find = createIterator(baseIteratorOptions, forEachIteratorOptions, { + 'init': '', + 'inLoop': 'if (callback(value, index, collection)) return value' + }); /** - * Creates a new function that will delay the execution of `func` until after - * `wait` milliseconds have elapsed since the last time it was invoked. Pass - * `true` for `immediate` to cause debounce to invoke `func` on the leading, - * instead of the trailing, edge of the `wait` timeout. Subsequent calls to - * the debounced function will return the result of the last `func` call. + * Iterates over a `collection`, executing the `callback` for each element in + * the `collection`. The `callback` is bound to `thisArg` and invoked with 3 + * arguments; (value, index|key, collection). Callbacks may exit iteration + * early by explicitly returning `false`. * * @static * @memberOf _ - * @category Functions - * @param {Function} func The function to debounce. - * @param {Number} wait The number of milliseconds to delay. - * @param {Boolean} immediate A flag to indicate execution is on the leading - * edge of the timeout. - * @returns {Function} Returns the new debounced function. + * @alias each + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Array|Object} Returns `collection`. * @example * - * var lazyLayout = _.debounce(calculateLayout, 300); - * jQuery(window).on('resize', lazyLayout); + * _([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 (order is not guaranteed) */ - function debounce(func, wait, immediate) { - var args, - result, - thisArg, - timeoutId; - - function delayed() { - timeoutId = null; - if (!immediate) { - func.apply(thisArg, args); - } - } - - return function() { - var isImmediate = immediate && !timeoutId; - args = arguments; - thisArg = this; - - clearTimeout(timeoutId); - timeoutId = setTimeout(delayed, wait); - - if (isImmediate) { - result = func.apply(thisArg, args); - } - return result; - }; - } + var forEach = createIterator(baseIteratorOptions, forEachIteratorOptions); /** - * Executes the `func` function after `wait` milliseconds. Additional arguments - * are passed to `func` when it is invoked. + * Creates an object composed of keys returned from running each element of + * `collection` through a `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 3 arguments; (value, index|key, collection). + * The `callback` argument may also be the name of a property to count by (e.g. 'length'). * * @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 `setTimeout` timeout id. + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|String} callback|property The function called per iteration + * or property name to group by. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Object} Returns the composed aggregate object. * @example * - * var log = _.bind(console.log, console); - * _.delay(log, 1000, 'logged later'); - * // => 'logged later' (Appears after one second.) + * _.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] } + * + * _.groupBy(['one', 'two', 'three'], 'length'); + * // => { '3': ['one', 'two'], '5': ['three'] } */ - function delay(func, wait) { - var args = slice.call(arguments, 2); - return setTimeout(function() { return func.apply(undefined, args); }, wait); - } + var groupBy = createIterator(baseIteratorOptions, countByIteratorOptions, { + 'inLoop': + 'prop = callback(value, index, collection);\n' + + '(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(value)' + }); /** - * Defers executing the `func` function until the current call stack has cleared. - * Additional arguments are passed to `func` when it is invoked. + * Invokes the method named by `methodName` on each element in the `collection`. + * 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 Functions - * @param {Function} func The function to defer. - * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. - * @returns {Number} Returns the `setTimeout` timeout id. + * @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 values returned from each invoked method. * @example * - * _.defer(function() { alert('deferred'); }); - * // returns from the function before `alert` is called + * _.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 defer(func) { - var args = slice.call(arguments, 1); - return setTimeout(function() { return func.apply(undefined, args); }, 1); - } + var invoke = createIterator(mapIteratorOptions, { + 'args': 'collection, methodName', + 'top': + 'var args = slice.call(arguments, 2),\n' + + ' isFunc = typeof methodName == \'function\'', + 'inLoop': { + 'array': + 'result[index] = (isFunc ? methodName : value[methodName]).apply(value, args)', + 'object': + 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + + '((isFunc ? methodName : value[methodName]).apply(value, args))' + } + }); /** - * Creates a new 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. + * Creates a new array of values by running each element in the `collection` + * through a `callback`. The `callback` is bound to `thisArg` and invoked with + * 3 arguments; (value, index|key, collection). * * @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. + * @alias collect + * @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 for the callback. + * @returns {Array} Returns a new array of elements returned by the callback. * @example * - * var fibonacci = _.memoize(function(n) { - * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); - * }); + * _.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) */ - function memoize(func, resolver) { - var cache = {}; - return function() { - var prop = resolver ? resolver.apply(this, arguments) : arguments[0]; - return hasOwnProperty.call(cache, prop) - ? cache[prop] - : (cache[prop] = func.apply(this, arguments)); - }; - } + var map = createIterator(baseIteratorOptions, mapIteratorOptions); /** - * Creates a new function that is restricted to one execution. Repeat calls to - * the function will return the value of the first call. + * Retrieves the value of a specified property from all elements in + * the `collection`. * * @static * @memberOf _ - * @category Functions - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted 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 initialize = _.once(createApplication); - * initialize(); - * initialize(); - * // Application is only created once. + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 }, + * { 'name': 'curly', 'age': 60 } + * ]; + * + * _.pluck(stooges, 'name'); + * // => ['moe', 'larry', 'curly'] */ - function once(func) { - var result, - ran = false; - - return function() { - if (ran) { - return result; - } - ran = true; - result = func.apply(this, arguments); - return result; - }; - } + var pluck = createIterator(mapIteratorOptions, { + 'args': 'collection, property', + 'inLoop': { + 'array': 'result[index] = value[property]', + 'object': 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '(value[property])' + } + }); /** - * Creates a new function that, when called, invokes `func` with any additional - * `partial` arguments prepended to those passed to the partially applied - * function. This method is similar `bind`, except it does **not** alter the - * `this` binding. + * Boils down a `collection` to a single value. The initial state of the + * reduction is `accumulator` and each successive step of it should be returned + * by the `callback`. The `callback` is bound to `thisArg` and invoked with 4 + * arguments; for arrays they are (accumulator, value, index|key, collection). * * @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. + * @alias foldl, inject + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} [accumulator] Initial value of the accumulator. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the accumulated value. * @example * - * var greet = function(greeting, name) { return greeting + ': ' + name; }; - * var hi = _.partial(greet, 'hi'); - * hi('moe'); - * // => 'hi: moe' + * var sum = _.reduce([1, 2, 3], function(memo, num) { return memo + num; }); + * // => 6 */ - function partial(func) { - var args = slice.call(arguments, 1), - argsLength = args.length; - - return function() { - var result, - others = arguments; - - if (others.length) { - args.length = argsLength; - push.apply(args, others); - } - result = args.length == 1 ? func.call(this, args[0]) : func.apply(this, args); - args.length = argsLength; - return result; - }; - } + var reduce = createIterator({ + 'args': 'collection, callback, accumulator, thisArg', + 'init': 'accumulator', + 'top': + 'var noaccum = arguments.length < 3;\n' + + 'if (thisArg) callback = iteratorBind(callback, thisArg)', + 'beforeLoop': { + 'array': 'if (noaccum) result = iteratee[++index]' + }, + 'inLoop': { + 'array': + 'result = callback(result, value, index, collection)', + 'object': + 'result = noaccum\n' + + ' ? (noaccum = false, value)\n' + + ' : callback(result, value, index, collection)' + } + }); /** - * Creates a new function that, when executed, will only call the `func` - * function at most once per every `wait` milliseconds. If the throttled - * function is invoked more than once during the `wait` timeout, `func` will - * also be called on the trailing edge of the timeout. Subsequent calls to the - * throttled function will return the result of the last `func` call. + * The right-associative version of `_.reduce`. * * @static * @memberOf _ - * @category Functions - * @param {Function} func The function to throttle. - * @param {Number} wait The number of milliseconds to throttle executions to. - * @returns {Function} Returns the new throttled function. + * @alias foldr + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} [accumulator] Initial value of the accumulator. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the accumulated value. * @example * - * var throttled = _.throttle(updatePosition, 100); - * jQuery(window).on('scroll', throttled); + * 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 throttle(func, wait) { - var args, - result, - thisArg, - timeoutId, - lastCalled = 0; - - function trailingCall() { - lastCalled = new Date; - timeoutId = null; - func.apply(thisArg, args); + function reduceRight(collection, callback, accumulator, thisArg) { + if (!collection) { + return accumulator; } - return function() { - var now = new Date, - remain = wait - (now - lastCalled); + var length = collection.length, + noaccum = arguments.length < 3; - args = arguments; - thisArg = this; + if(thisArg) { + callback = iteratorBind(callback, thisArg); + } + // Opera 10.53-10.60 JITted `length >>> 0` returns the wrong value for negative numbers + if (length > -1 && length === length >>> 0) { + var iteratee = noCharByIndex && toString.call(collection) == stringClass + ? collection.split('') + : collection; - if (remain <= 0) { - lastCalled = now; - result = func.apply(thisArg, args); - } - else if (!timeoutId) { - timeoutId = setTimeout(trailingCall, remain); + if (length && noaccum) { + accumulator = iteratee[--length]; } - return result; - }; - } - - /** - * Create a new function that passes the `func` function to the `wrapper` - * function as its first argument. Additional arguments are appended to those - * passed to the `wrapper` function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to wrap. - * @param {Function} wrapper The wrapper function. - * @param {Mixed} [arg1, arg2, ...] Arguments to append to those passed to the wrapper. - * @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(func, wrapper) { - return function() { - var args = [func]; - if (arguments.length) { - push.apply(args, arguments); + while (length--) { + accumulator = callback(accumulator, iteratee[length], length, collection); } - return wrapper.apply(this, args); - }; - } + return accumulator; + } - /*--------------------------------------------------------------------------*/ + var prop, + props = keys(collection); - /** - * Create a shallow clone of the `value`. Any nested objects or arrays will be - * assigned by reference and not cloned. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to clone. - * @returns {Mixed} Returns the cloned `value`. - * @example - * - * _.clone({ 'name': 'moe' }); - * // => { 'name': 'moe' }; - */ - function clone(value) { - return objectTypes[typeof value] && value !== null - ? (isArray(value) ? value.slice() : extend({}, value)) - : value; + length = props.length; + if (length && noaccum) { + accumulator = collection[props[--length]]; + } + while (length--) { + prop = props[length]; + accumulator = callback(accumulator, collection[prop], prop, collection); + } + return accumulator; } /** - * Assigns missing properties on `object` with default values from the defaults - * objects. Once a property is set, additional defaults of the same property - * will be ignored. + * The opposite of `_.filter`, this method returns the values of a + * `collection` that `callback` does **not** return truthy for. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to populate. - * @param {Object} [defaults1, defaults2, ...] The defaults objects to apply to `object`. - * @returns {Object} Returns `object`. + * @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 for the callback. + * @returns {Array} Returns a new array of elements that did **not** pass the callback check. * @example * - * var iceCream = { 'flavor': 'chocolate' }; - * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'rainbow' }); - * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' } + * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [1, 3, 5] */ - var defaults = createIterator(extendIteratorOptions, { - 'inLoop': 'if (object[index] == null)' + extendIteratorOptions.inLoop + var reject = createIterator(baseIteratorOptions, filterIteratorOptions, { + 'inLoop': '!' + filterIteratorOptions.inLoop }); /** - * Copies enumerable properties from the source objects to the `destination` object. - * Subsequent sources will overwrite propery assignments of previous sources. + * 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 3 arguments; (value, index|key, collection). * * @static * @memberOf _ - * @category Objects - * @param {Object} object The destination object. - * @param {Object} [source1, source2, ...] The source objects. - * @returns {Object} Returns the destination object. + * @alias any + * @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 for the callback. + * @returns {Boolean} Returns `true` if any element passes the callback check, else `false`. * @example * - * _.extend({ 'name': 'moe' }, { 'age': 40 }); - * // => { 'name': 'moe', 'age': 40 } + * _.some([null, 0, 'yes', false]); + * // => true */ - var extend = createIterator(extendIteratorOptions); + var some = createIterator(baseIteratorOptions, everyIteratorOptions, { + 'init': 'false', + 'inLoop': everyIteratorOptions.inLoop.replace('!', '') + }); /** - * Iterates over `object`'s own and inherited enumerable properties, executing - * the `callback` for each property. The `callback` is bound to `thisArg` and - * invoked with 3 arguments; (value, key, object). + * Creates a new array, stable sorted in ascending order by the results of + * running each element of `collection` through a `callback`. The `callback` + * is bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection). + * The `callback` argument may also be the name of a property to sort by (e.g. 'length'). * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to iterate over. - * @param {Function} callback The function called per iteration. + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|String} callback|property The function called per iteration + * or property name to sort by. * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Object} Returns the `object`. + * @returns {Array} Returns a new array of sorted elements. * @example * - * function Dog(name) { - * this.name = name; - * } + * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); }); + * // => [3, 1, 2] * - * Dog.prototype.bark = function() { - * alert('Woof, woof!'); - * }; + * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math); + * // => [3, 1, 2] * - * _.forIn(new Dog('Dagny'), function(value, key) { - * alert(key); - * }); - * // => alerts 'name' and 'bark' (order is not guaranteed) + * _.sortBy(['larry', 'brendan', 'moe'], 'length'); + * // => ['moe', 'larry', 'brendan'] */ - var forIn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions, { - 'useHas': false + var sortBy = createIterator(baseIteratorOptions, countByIteratorOptions, mapIteratorOptions, { + 'inLoop': { + 'array': + 'result[index] = {\n' + + ' criteria: callback(value, index, collection),\n' + + ' index: index,\n' + + ' value: value\n' + + '}', + 'object': + 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '({\n' + + ' criteria: callback(value, index, collection),\n' + + ' index: index,\n' + + ' value: value\n' + + '})' + }, + 'bottom': + 'result.sort(compareAscending);\n' + + 'length = result.length;\n' + + 'while (length--) {\n' + + ' result[length] = result[length].value\n' + + '}' }); /** - * Iterates over `object`'s own enumerable properties, executing the `callback` - * for each property. The `callback` is bound to `thisArg` and invoked with 3 - * arguments; (value, key, object). + * Converts the `collection`, to an array. Useful for converting the + * `arguments` object. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Object} Returns the `object`. + * @category Collections + * @param {Array|Object|String} collection The collection to convert. + * @returns {Array} Returns the new converted array. * @example * - * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { - * alert(key); - * }); - * // => alerts '0', '1', and 'length' (order is not guaranteed) + * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4); + * // => [2, 3, 4] */ - var forOwn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions); + function toArray(collection) { + if (!collection) { + return []; + } + if (collection.toArray && isFunction(collection.toArray)) { + return collection.toArray(); + } + var length = collection.length; + if (length > -1 && length === length >>> 0) { + return (noArraySliceOnStrings ? toString.call(collection) == stringClass : typeof collection == 'string') + ? collection.split('') + : slice.call(collection); + } + return values(collection); + } /** - * Produces a sorted array of the enumerable properties, own and inherited, - * of `object` that have function values. + * Examines each element in a `collection`, returning an array of all elements + * that contain the given `properties`. * * @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. + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Object} properties The object of properties/values to filter by. + * @returns {Array} Returns a new array of elements that contain the given `properties`. * @example * - * _.functions(_); - * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 }, + * { 'name': 'curly', 'age': 60 } + * ]; + * + * _.where(stooges, { 'age': 40 }); + * // => [{ 'name': 'moe', 'age': 40 }] */ - var functions = createIterator({ - 'args': 'object', - 'init': '[]', - 'useHas': false, - 'inLoop': 'if (toString.call(object[index]) == funcClass) result.push(index)', - 'bottom': 'result.sort()' + var where = createIterator(filterIteratorOptions, { + 'args': 'collection, properties', + 'top': + 'var props = [];\n' + + 'forIn(properties, function(value, prop) { props.push(prop) });\n' + + 'var propsLength = props.length', + 'inLoop': + 'for (var prop, pass = true, propIndex = 0; propIndex < propsLength; propIndex++) {\n' + + ' prop = props[propIndex];\n' + + ' if (!(pass = value[prop] === properties[prop])) break\n' + + '}\n' + + 'pass && result.push(value)' }); + /*--------------------------------------------------------------------------*/ + /** - * Checks if the specified object `property` exists and is a direct property, - * instead of an inherited property. + * Creates a new array with all falsey values of `array` removed. The values + * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey. * * @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`. + * @category Arrays + * @param {Array} array The array to compact. + * @returns {Array} Returns a new filtered array. * @example * - * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); - * // => true + * _.compact([0, 1, false, 2, '', 3]); + * // => [1, 2, 3] */ - function has(object, property) { - return hasOwnProperty.call(object, property); + function compact(array) { + var result = []; + if (!array) { + return result; + } + var index = -1, + length = array.length; + + while (++index < length) { + if (array[index]) { + result.push(array[index]); + } + } + return result; } /** - * Checks if `value` is an `arguments` object. + * Creates a new array of `array` elements not present in the other arrays + * using strict equality for comparisons, i.e. `===`. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is an `arguments` object, else `false`. + * @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 * - * (function() { return _.isArguments(arguments); })(1, 2, 3); - * // => true + * _.difference([1, 2, 3, 4, 5], [5, 2, 10]); + * // => [1, 3, 4] + */ + function difference(array) { + var result = []; + if (!array) { + return result; + } + var index = -1, + length = array.length, + flattened = concat.apply(result, arguments), + contains = cachedContains(flattened, length); + + while (++index < length) { + if (!contains(array[index])) { + result.push(array[index]); + } + } + return result; + } + + /** + * Gets the first element of the `array`. Pass `n` to return the first `n` + * elements of the `array`. * - * _.isArguments([1, 2, 3]); - * // => false + * @static + * @memberOf _ + * @alias head, take + * @category Arrays + * @param {Array} array The array to query. + * @param {Number} [n] The number of elements to return. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `n`. + * @returns {Mixed} Returns the first element or an array of the first `n` + * elements of `array`. + * @example + * + * _.first([5, 4, 3, 2, 1]); + * // => 5 */ - var isArguments = function(value) { - return toString.call(value) == '[object Arguments]'; - }; - // fallback for browser like IE < 9 which detect `arguments` as `[object Object]` - if (!isArguments(arguments)) { - isArguments = function(value) { - return !!(value && hasOwnProperty.call(value, 'callee')); - }; + function first(array, n, guard) { + if (array) { + return (n == null || guard) ? array[0] : slice.call(array, 0, n); + } } /** - * Checks if `value` is an array. + * Flattens a nested array (the nesting can be to any depth). If `shallow` is + * truthy, `array` will only be flattened a single level. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is an array, else `false`. + * @category Arrays + * @param {Array} array The array to compact. + * @param {Boolean} shallow A flag to indicate only flattening a single level. + * @returns {Array} Returns a new flattened array. * @example * - * (function() { return _.isArray(arguments); })(); - * // => false + * _.flatten([1, [2], [3, [[4]]]]); + * // => [1, 2, 3, 4]; * - * _.isArray([1, 2, 3]); - * // => true + * _.flatten([1, [2], [3, [[4]]]], true); + * // => [1, 2, 3, [[4]]]; */ - var isArray = nativeIsArray || function(value) { - return toString.call(value) == arrayClass; - }; + function flatten(array, shallow) { + var result = []; + if (!array) { + return result; + } + var value, + index = -1, + length = array.length; + + while (++index < length) { + value = array[index]; + if (isArray(value)) { + push.apply(result, shallow ? value : flatten(value)); + } else { + result.push(value); + } + } + return result; + } /** - * Checks if `value` is a boolean (`true` or `false`) value. + * 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 `isSorted` will run a faster binary search. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a boolean value, else `false`. + * @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 start searching from or + * `true` to perform a binary search on a sorted `array`. + * @returns {Number} Returns the index of the matched value or `-1`. * @example * - * _.isBoolean(null); - * // => false + * _.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 isBoolean(value) { - return value === true || value === false || toString.call(value) == boolClass; + function indexOf(array, value, fromIndex) { + if (!array) { + return -1; + } + var index = -1, + length = array.length; + + if (fromIndex) { + if (typeof fromIndex == 'number') { + index = (fromIndex < 0 ? Math.max(0, length + fromIndex) : fromIndex) - 1; + } else { + index = sortedIndex(array, value); + return array[index] === value ? index : -1; + } + } + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; } /** - * Checks if `value` is a date. + * Gets all but the last element of `array`. Pass `n` to exclude the last `n` + * elements from the result. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a date, else `false`. + * @category Arrays + * @param {Array} array The array to query. + * @param {Number} [n] The number of elements to return. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `n`. + * @returns {Array} Returns all but the last element or `n` elements of `array`. * @example * - * _.isDate(new Date); - * // => true + * _.initial([3, 2, 1]); + * // => [3, 2] */ - function isDate(value) { - return toString.call(value) == dateClass; + function initial(array, n, guard) { + if (!array) { + return []; + } + return slice.call(array, 0, -((n == null || guard) ? 1 : n)); } /** - * Checks if `value` is a DOM element. + * Computes the intersection of all the passed-in arrays using strict equality + * for comparisons, i.e. `===`. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a DOM element, else `false`. + * @category Arrays + * @param {Array} [array1, array2, ...] Arrays to process. + * @returns {Array} Returns a new array of unique elements, in order, that are + * present in **all** of the arrays. * @example * - * _.isElement(document.body); - * // => true + * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); + * // => [1, 2] */ - function isElement(value) { - return !!(value && value.nodeType == 1); + function intersection(array) { + var result = []; + if (!array) { + return result; + } + var value, + argsLength = arguments.length, + cache = [], + index = -1, + length = array.length; + + array: while (++index < length) { + value = array[index]; + if (indexOf(result, value) < 0) { + for (var argsIndex = 1; argsIndex < argsLength; argsIndex++) { + if (!(cache[argsIndex] || (cache[argsIndex] = cachedContains(arguments[argsIndex])))(value)) { + continue array; + } + } + result.push(value); + } + } + return result; } /** - * Checks if `value` is empty. Arrays or strings with a length of `0` and - * objects with no own enumerable properties are considered "empty". + * Gets the last element of the `array`. Pass `n` to return the lasy `n` + * elementsvof the `array`. * * @static * @memberOf _ - * @category Objects - * @param {Array|Object|String} value The value to inspect. - * @returns {Boolean} Returns `true` if the `value` is empty, else `false`. + * @category Arrays + * @param {Array} array The array to query. + * @param {Number} [n] The number of elements to return. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `n`. + * @returns {Mixed} Returns the last element or an array of the last `n` + * elements of `array`. * @example * - * _.isEmpty([1, 2, 3]); - * // => false - * - * _.isEmpty({}); - * // => true + * _.last([3, 2, 1]); + * // => 1 */ - var isEmpty = createIterator({ - 'args': 'value', - 'init': 'true', - 'top': - 'var className = toString.call(value);\n' + - 'if (className == arrayClass || className == stringClass) return !value.length', - 'inLoop': { - 'object': 'return false' + function last(array, n, guard) { + if (array) { + var length = array.length; + return (n == null || guard) ? array[length - 1] : slice.call(array, -n || length); } - }); + } /** - * Performs a deep comparison between two values to determine if they are - * equivalent to each other. + * Gets the index at which the last occurrence of `value` is found using + * strict equality for comparisons, i.e. `===`. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} a The value to compare. - * @param {Mixed} b The other value to compare. - * @param {Array} [stack] Internally used to keep track of "seen" objects to - * avoid circular references. - * @returns {Boolean} Returns `true` if the values are equvalent, else `false`. + * @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 start searching from. + * @returns {Number} Returns the index of the matched value or `-1`. * @example * - * var moe = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; - * var clone = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; - * - * moe == clone; - * // => false + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); + * // => 4 * - * _.isEqual(moe, clone); - * // => true + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3); + * // => 1 */ - function isEqual(a, b, stack) { - stack || (stack = []); - - // exit early for identical values - if (a === b) { - // treat `+0` vs. `-0` as not equal - return a !== 0 || (1 / a == 1 / b); - } - // a strict comparison is necessary because `undefined == null` - if (a == null || b == null) { - return a === b; - } - // unwrap any wrapped objects - if (a._chain) { - a = a._wrapped; - } - if (b._chain) { - b = b._wrapped; + function lastIndexOf(array, value, fromIndex) { + if (!array) { + return -1; } - // invoke a custom `isEqual` method if one is provided - if (a.isEqual && toString.call(a.isEqual) == funcClass) { - return a.isEqual(b); + var index = array.length; + if (fromIndex && typeof fromIndex == 'number') { + index = (fromIndex < 0 ? Math.max(0, index + fromIndex) : Math.min(fromIndex, index - 1)) + 1; } - if (b.isEqual && toString.call(b.isEqual) == funcClass) { - return b.isEqual(a); + while (index--) { + if (array[index] === value) { + return index; + } } - // compare [[Class]] names - var className = toString.call(a); - if (className != toString.call(b)) { - return false; + return -1; + } + + /** + * 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 3 arguments; (value, index, array). + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to iterate over. + * @param {Function} [callback] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the maximum value. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 }, + * { 'name': 'curly', 'age': 60 } + * ]; + * + * _.max(stooges, function(stooge) { return stooge.age; }); + * // => { 'name': 'curly', 'age': 60 }; + */ + function max(array, callback, thisArg) { + var computed = -Infinity, + result = computed; + + if (!array) { + return result; } - switch (className) { - // strings, numbers, dates, and booleans are compared by value - case stringClass: - // primitives and their corresponding object instances are equivalent; - // thus, `'5'` is quivalent to `new String('5')` - return a == String(b); + var current, + index = -1, + length = array.length; - 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); + if (!callback) { + while (++index < length) { + if (array[index] > result) { + result = array[index]; + } + } + return result; + } + if (thisArg) { + callback = iteratorBind(callback, thisArg); + } + while (++index < length) { + current = callback(array[index], index, array); + if (current > computed) { + computed = current; + result = array[index]; + } + } + return result; + } - case boolClass: - case dateClass: - // coerce dates and booleans to numeric values, dates to milliseconds and booleans to 1 or 0; - // treat invalid dates coerced to `NaN` as not equal - return +a == +b; + /** + * 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 3 arguments; (value, index, array). + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to iterate over. + * @param {Function} [callback] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the minimum value. + * @example + * + * _.min([10, 5, 100, 2, 1000]); + * // => 2 + */ + function min(array, callback, thisArg) { + var computed = Infinity, + result = computed; - // regexps are compared by their source and flags - case regexpClass: - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; + if (!array) { + return result; } - if (typeof a != 'object' || typeof b != 'object') { - return false; + var current, + index = -1, + length = array.length; + + if (!callback) { + while (++index < length) { + if (array[index] < result) { + result = array[index]; + } + } + return result; } - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = stack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (stack[length] == a) { - return true; + if (thisArg) { + callback = iteratorBind(callback, thisArg); + } + while (++index < length) { + current = callback(array[index], index, array); + if (current < computed) { + computed = current; + result = array[index]; } } + return result; + } + /** + * Creates an array of numbers (positive and/or negative) progressing from + * `start` up to but not including `stop`. This method is a port of Python's + * `range()` function. See http://docs.python.org/library/functions.html#range. + * + * @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 descrement 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://www.youtube.com/watch?v=XAqIpGU8ZZk#t=16m27s var index = -1, - result = true, - size = 0; + length = Math.max(0, Math.ceil((end - start) / step)), + result = Array(length); - // add the first collection to the stack of traversed objects - stack.push(a); + while (++index < length) { + result[index] = start; + start += step; + } + return result; + } - // recursively compare objects and arrays - if (className == arrayClass) { - // compare array lengths to determine if a deep comparison is necessary - size = a.length; - result = size == b.length; + /** + * The opposite of `_.initial`, this method gets all but the first value of + * `array`. Pass `n` to exclude the first `n` values from the result. + * + * @static + * @memberOf _ + * @alias tail + * @category Arrays + * @param {Array} array The array to query. + * @param {Number} [n] The number of elements to return. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `n`. + * @returns {Array} Returns all but the first value or `n` values of `array`. + * @example + * + * _.rest([3, 2, 1]); + * // => [2, 1] + */ + function rest(array, n, guard) { + if (!array) { + return []; + } + return slice.call(array, (n == null || guard) ? 1 : n); + } - if (result) { - // deep compare the contents, ignoring non-numeric properties - while (size--) { - if (!(result = isEqual(a[size], b[size], stack))) { - break; - } - } + /** + * Creates a new 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 Arrays + * @param {Array} array The array to shuffle. + * @returns {Array} Returns a new shuffled array. + * @example + * + * _.shuffle([1, 2, 3, 4, 5, 6]); + * // => [4, 1, 6, 3, 5, 2] + */ + function shuffle(array) { + if (!array) { + return []; + } + var rand, + index = -1, + length = array.length, + result = Array(length); + + while (++index < length) { + rand = Math.floor(Math.random() * (index + 1)); + result[index] = result[rand]; + result[rand] = array[index]; + } + return result; + } + + /** + * 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 1 argument; (value). + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to iterate over. + * @param {Mixed} value The value to evaluate. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Number} Returns the index at which the value should be inserted + * into `array`. + * @example + * + * _.sortedIndex([20, 30, 40], 35); + * // => 2 + * + * var dict = { + * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'thirty-five': 35, 'fourty': 40 } + * }; + * + * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { + * return dict.wordToNumber[word]; + * }); + * // => 2 + * + * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { + * return this.wordToNumber[word]; + * }, dict); + * // => 2 + */ + function sortedIndex(array, value, callback, thisArg) { + if (!array) { + return 0; + } + var mid, + low = 0, + high = array.length; + + if (callback) { + if (thisArg) { + callback = bind(callback, thisArg); + } + value = callback(value); + while (low < high) { + mid = (low + high) >>> 1; + callback(array[mid]) < value ? low = mid + 1 : high = mid; } } else { - // objects with different constructors are not equivalent - if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) { - return false; + while (low < high) { + mid = (low + high) >>> 1; + array[mid] < value ? low = mid + 1 : high = mid; } - // deep compare objects. - for (var prop in a) { - if (hasOwnProperty.call(a, prop)) { - // count the number of properties. - size++; - // deep compare each property value. - if (!(result = hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack))) { - break; - } - } + } + 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() { + var index = -1, + result = [], + flattened = concat.apply(result, arguments), + length = flattened.length; + + while (++index < length) { + if (indexOf(result, flattened[index]) < 0) { + result.push(flattened[index]); } - // ensure both objects have the same number of properties - if (result) { - for (prop in b) { - // Adobe's JS engine, embedded in applications like InDesign, has a - // bug that causes `!size--` to throw an error so it must be wrapped - // in parentheses. - // https://github.com/documentcloud/underscore/issues/355 - if (hasOwnProperty.call(b, prop) && !(size--)) { - break; - } - } - result = !size; + } + return result; + } + + /** + * 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 a callback` before uniqueness is computed. + * The `callback` is bound to `thisArg` and invoked with 3 arguments; (value, index, array). + * + * @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} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the 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([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); }); + * // => [1, 2, 3] + * + * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math); + * // => [1, 2, 3] + */ + function uniq(array, isSorted, callback, thisArg) { + var result = []; + if (!array) { + return result; + } + var computed, + index = -1, + length = array.length, + seen = []; + + // juggle arguments + if (typeof isSorted == 'function') { + thisArg = callback; + callback = isSorted; + isSorted = false; + } + if (!callback) { + callback = identity; + } else if (thisArg) { + callback = iteratorBind(callback, thisArg); + } + while (++index < length) { + computed = callback(array[index], index, array); + if (isSorted + ? !index || seen[seen.length - 1] !== computed + : indexOf(seen, computed) < 0 + ) { + seen.push(computed); + result.push(array[index]); } - // handle JScript [[DontEnum]] bug - if (result && hasDontEnumBug) { - while (++index < 7) { - prop = shadowed[index]; - if (hasOwnProperty.call(a, prop)) { - if (!(result = hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack))) { - break; - } - } - } + } + return result; + } + + /** + * Creates a new 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) { + var result = []; + if (!array) { + return result; + } + var index = -1, + length = array.length, + contains = cachedContains(arguments, 1, 20); + + while (++index < length) { + if (!contains(array[index])) { + result.push(array[index]); } } - // remove the first collection from the stack of traversed objects - stack.pop(); return result; } /** - * Checks if `value` is a finite number. - * Note: This is not the same as native `isFinite`, which will return true for - * booleans and other values. See http://es5.github.com/#x15.1.2.5. + * 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', 'curly'], [30, 40, 50], [true, false, false]); + * // => [['moe', 30, true], ['larry', 40, false], ['curly', 50, false]] + */ + function zip(array) { + if (!array) { + return []; + } + var index = -1, + length = max(pluck(arguments, 'length')), + result = Array(length); + + while (++index < length) { + result[index] = pluck(arguments, index); + } + return result; + } + + /** + * Creates an object composed from an array of `keys` and an array of `values`. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a finite number, else `false`. + * @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 * - * _.isFinite(-101); - * // => true - * - * _.isFinite('10'); - * // => false - * - * _.isFinite(Infinity); - * // => false + * _.zipObject(['moe', 'larry', 'curly'], [30, 40, 50]); + * // => { 'moe': 30, 'larry': 40, 'curly': 50 } */ - function isFinite(value) { - return nativeIsFinite(value) && toString.call(value) == numberClass; + function zipObject(keys, values) { + if (!keys) { + return {}; + } + var index = -1, + length = keys.length, + result = {}; + + values || (values = []); + while (++index < length) { + result[keys[index]] = values[index]; + } + return result; } + /*--------------------------------------------------------------------------*/ + /** - * Checks if `value` is a function. + * Creates a new function that is restricted to executing only after it is + * called `n` times. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a function, else `false`. + * @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 * - * _.isFunction(''.concat); - * // => true + * var renderNotes = _.after(notes.length, render); + * _.forEach(notes, function(note) { + * note.asyncSave({ 'success': renderNotes }); + * }); + * // `renderNotes` is run once, after all notes have saved */ - function isFunction(value) { - return toString.call(value) == funcClass; + function after(n, func) { + if (n < 1) { + return func(); + } + return function() { + if (--n < 1) { + return func.apply(this, arguments); + } + }; } /** - * Checks if `value` is the language type of Object. - * (e.g. arrays, functions, objects, regexps, `new Number(0)`, and `new String('')`) + * Creates a new 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. Lazy defined methods may be bound by passing + * the object they are bound to as `func` and the method name as `thisArg`. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is an object, else `false`. + * @category Functions + * @param {Function|Object} func The function to bind or the object the method belongs to. + * @param {Mixed} [thisArg] The `this` binding of `func` or the method name. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new bound function. * @example * - * _.isObject({}); - * // => true + * // basic bind + * var func = function(greeting) { + * return greeting + ' ' + this.name; + * }; * - * _.isObject(1); - * // => false + * func = _.bind(func, { 'name': 'moe' }, 'hi'); + * func(); + * // => 'hi moe' + * + * // lazy bind + * var object = { + * 'name': 'moe', + * 'greet': function(greeting) { + * return greeting + ' ' + this.name; + * } + * }; + * + * var func = _.bind(object, 'greet', 'hi'); + * func(); + * // => 'hi moe' + * + * object.greet = function(greeting) { + * return greeting + ', ' + this.name + '!'; + * }; + * + * func(); + * // => 'hi, moe!' */ - function isObject(value) { - // check if the value is the ECMAScript language type of Object - // http://es5.github.com/#x8 - return objectTypes[typeof value] && value !== null; + function bind(func, thisArg) { + var methodName, + isFunc = isFunction(func); + + // juggle arguments + if (!isFunc) { + methodName = thisArg; + thisArg = func; + } + // use `Function#bind` if it exists and is fast + // (in V8 `Function#bind` is slower except when partially applied) + else if (isBindFast || (nativeBind && arguments.length > 2)) { + return nativeBind.call.apply(nativeBind, arguments); + } + + var partialArgs = slice.call(arguments, 2); + + function bound() { + // `Function#bind` spec + // http://es5.github.com/#x15.3.4.5 + var args = arguments, + thisBinding = thisArg; + + if (!isFunc) { + func = thisArg[methodName]; + } + if (partialArgs.length) { + args = args.length + ? partialArgs.concat(slice.call(args)) + : partialArgs; + } + if (this instanceof bound) { + // get `func` instance if `bound` is invoked in a `new` expression + noop.prototype = func.prototype; + thisBinding = new noop; + + // mimic the constructor's `return` behavior + // http://es5.github.com/#x13.2.2 + var result = func.apply(thisBinding, args); + return result && objectTypes[typeof result] + ? result + : thisBinding + } + return func.apply(thisBinding, args); + } + return bound; } /** - * 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. + * Binds methods on `object` to `object`, overwriting the existing method. + * If no method names are provided, all the function properties of `object` + * will be bound. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is `NaN`, else `false`. + * @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 * - * _.isNaN(NaN); - * // => true + * var buttonView = { + * 'label': 'lodash', + * 'onClick': function() { alert('clicked: ' + this.label); } + * }; * - * _.isNaN(new Number(NaN)); - * // => true + * _.bindAll(buttonView); + * jQuery('#lodash_button').on('click', buttonView.onClick); + * // => When the button is clicked, `this.label` will have the correct value + */ + var bindAll = createIterator({ + 'useHas': false, + 'useStrict': false, + 'args': 'object', + 'init': 'object', + 'top': + 'var funcs = arguments,\n' + + ' length = funcs.length;\n' + + 'if (length > 1) {\n' + + ' for (var index = 1; index < length; index++) {\n' + + ' result[funcs[index]] = bind(result[funcs[index]], result)\n' + + ' }\n' + + ' return result\n' + + '}', + 'inLoop': + 'if (isFunction(result[index])) {\n' + + ' result[index] = bind(result[index], result)\n' + + '}' + }); + + /** + * Creates a new function that is the composition of the passed functions, + * where each function consumes the return value of the function that follows. + * In math terms, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. * - * isNaN(undefined); - * // => true + * @static + * @memberOf _ + * @category Functions + * @param {Function} [func1, func2, ...] Functions to compose. + * @returns {Function} Returns the new composed function. + * @example * - * _.isNaN(undefined); - * // => false + * var greet = function(name) { return 'hi: ' + name; }; + * var exclaim = function(statement) { return statement + '!'; }; + * var welcome = _.compose(exclaim, greet); + * welcome('moe'); + * // => 'hi: moe!' */ - 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 toString.call(value) == numberClass && value != +value + function compose() { + var funcs = arguments; + return function() { + var args = arguments, + length = funcs.length; + + while (length--) { + args = [funcs[length].apply(this, args)]; + } + return args[0]; + }; } /** - * Checks if `value` is `null`. + * Creates a new function that will delay the execution of `func` until after + * `wait` milliseconds have elapsed since the last time it was invoked. Pass + * `true` for `immediate` to cause debounce to invoke `func` on the leading, + * instead of the trailing, edge of the `wait` timeout. Subsequent calls to + * the debounced function will return the result of the last `func` call. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is `null`, else `false`. + * @category Functions + * @param {Function} func The function to debounce. + * @param {Number} wait The number of milliseconds to delay. + * @param {Boolean} immediate A flag to indicate execution is on the leading + * edge of the timeout. + * @returns {Function} Returns the new debounced function. * @example * - * _.isNull(null); - * // => true - * - * _.isNull(undefined); - * // => false + * var lazyLayout = _.debounce(calculateLayout, 300); + * jQuery(window).on('resize', lazyLayout); */ - function isNull(value) { - return value === null; + function debounce(func, wait, immediate) { + var args, + result, + thisArg, + timeoutId; + + function delayed() { + timeoutId = null; + if (!immediate) { + func.apply(thisArg, args); + } + } + + return function() { + var isImmediate = immediate && !timeoutId; + args = arguments; + thisArg = this; + + clearTimeout(timeoutId); + timeoutId = setTimeout(delayed, wait); + + if (isImmediate) { + result = func.apply(thisArg, args); + } + return result; + }; } /** - * Checks if `value` is a number. + * Executes the `func` function after `wait` milliseconds. Additional arguments + * will be passed to `func` when it is invoked. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a number, else `false`. + * @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 `setTimeout` timeout id. * @example * - * _.isNumber(8.4 * 5; - * // => true + * var log = _.bind(console.log, console); + * _.delay(log, 1000, 'logged later'); + * // => 'logged later' (Appears after one second.) */ - function isNumber(value) { - return toString.call(value) == numberClass; + function delay(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function() { return func.apply(undefined, args); }, wait); } /** - * Checks if `value` is a regular expression. + * 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 Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a regular expression, else `false`. + * @category Functions + * @param {Function} func The function to defer. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. + * @returns {Number} Returns the `setTimeout` timeout id. * @example * - * _.isRegExp(/moe/); - * // => true + * _.defer(function() { alert('deferred'); }); + * // returns from the function before `alert` is called */ - function isRegExp(value) { - return toString.call(value) == regexpClass; + function defer(func) { + var args = slice.call(arguments, 1); + return setTimeout(function() { return func.apply(undefined, args); }, 1); } /** - * Checks if `value` is a string. + * Creates a new 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. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a string, else `false`. + * @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 * - * _.isString('moe'); - * // => true + * var fibonacci = _.memoize(function(n) { + * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); + * }); */ - function isString(value) { - return toString.call(value) == stringClass; + function memoize(func, resolver) { + var cache = {}; + return function() { + var prop = resolver ? resolver.apply(this, arguments) : arguments[0]; + return hasOwnProperty.call(cache, prop) + ? cache[prop] + : (cache[prop] = func.apply(this, arguments)); + }; } /** - * Checks if `value` is `undefined`. + * Creates a new function that is restricted to one execution. Repeat calls to + * the function will return the value of the first call. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is `undefined`, else `false`. + * @category Functions + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. * @example * - * _.isUndefined(void 0); - * // => true + * var initialize = _.once(createApplication); + * initialize(); + * initialize(); + * // Application is only created once. */ - function isUndefined(value) { - return value === undefined; + function once(func) { + var result, + ran = false; + + 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; + }; } /** - * Produces an array of object`'s own enumerable property names. + * Creates a new function that, when called, invokes `func` with any additional + * `partial` arguments prepended to those passed to the new function. This method + * is similar `bind`, except it does **not** alter the `this` binding. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property names. + * @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 * - * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); - * // => ['one', 'two', 'three'] (order is not guaranteed) + * var greet = function(greeting, name) { return greeting + ': ' + name; }; + * var hi = _.partial(greet, 'hi'); + * hi('moe'); + * // => 'hi: moe' */ - var keys = !nativeKeys ? shimKeys : function(object) { - // avoid iterating over the `prototype` property - return typeof object == 'function' - ? shimKeys(object) - : nativeKeys(object); - }; + function partial(func) { + var args = slice.call(arguments, 1), + argsLength = args.length; - /** - * Creates an object composed of the specified properties. Property names may - * be specified as individual arguments or as arrays of property names. - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The object to pluck. - * @param {Object} [prop1, prop2, ...] The properties to pick. - * @returns {Object} Returns an object composed of the picked properties. - * @example - * - * _.pick({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'name', 'age'); - * // => { 'name': 'moe', 'age': 40 } - */ - function pick(object) { - var prop, - index = 0, - props = concat.apply(ArrayProto, arguments), - length = props.length, - result = {}; + return function() { + var result, + others = arguments; - // start `index` at `1` to skip `object` - while (++index < length) { - prop = props[index]; - if (prop in object) { - result[prop] = object[prop]; + if (others.length) { + args.length = argsLength; + push.apply(args, others); } - } - return result; + result = args.length == 1 ? func.call(this, args[0]) : func.apply(this, args); + args.length = argsLength; + return result; + }; } /** - * Gets the size of `value` by returning `value.length` if `value` is a string - * or array, or the number of own enumerable properties if `value` is an object. + * Creates a new function that, when executed, will only call the `func` + * function at most once per every `wait` milliseconds. If the throttled + * function is invoked more than once during the `wait` timeout, `func` will + * also be called on the trailing edge of the timeout. Subsequent calls to the + * throttled function will return the result of the last `func` call. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Array|Object|String} value The value to inspect. - * @returns {Number} Returns `value.length` if `value` is a string or array, - * or the number of own enumerable properties if `value` is an object. + * @category Functions + * @param {Function} func The function to throttle. + * @param {Number} wait The number of milliseconds to throttle executions to. + * @returns {Function} Returns the new throttled function. * @example * - * _.size([1, 2]); - * // => 2 - * - * _.size({ 'one': 1, 'two': 2, 'three': 3 }); - * // => 3 - * - * _.size('curly'); - * // => 5 + * var throttled = _.throttle(updatePosition, 100); + * jQuery(window).on('scroll', throttled); */ - function size(value) { - if (!value) { - return 0; + function throttle(func, wait) { + var args, + result, + thisArg, + timeoutId, + lastCalled = 0; + + function trailingCall() { + lastCalled = new Date; + timeoutId = null; + func.apply(thisArg, args); } - var length = value.length; - return length === length >>> 0 ? value.length : keys(value).length; + + return function() { + var now = new Date, + remain = wait - (now - lastCalled); + + args = arguments; + thisArg = this; + + if (remain <= 0) { + lastCalled = now; + result = func.apply(thisArg, args); + } + else if (!timeoutId) { + timeoutId = setTimeout(trailingCall, remain); + } + return result; + }; } /** - * Produces an array of `object`'s own enumerable property values. + * Creates a new function that passes `value` to the `wrapper` function as its + * first argument. Additional arguments passed to the new function are appended + * to those passed to the `wrapper` function. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property values. + * @category Functions + * @param {Mixed} value The value to wrap. + * @param {Function} wrapper The wrapper function. + * @returns {Function} Returns the new function. * @example * - * _.values({ 'one': 1, 'two': 2, 'three': 3 }); - * // => [1, 2, 3] + * var hello = function(name) { return 'hello: ' + name; }; + * hello = _.wrap(hello, function(func) { + * return 'before, ' + func('moe') + ', after'; + * }); + * hello(); + * // => 'before, hello: moe, after' */ - var values = createIterator({ - 'args': 'object', - 'init': '[]', - 'inLoop': 'result.push(object[index])' - }); + function wrap(value, wrapper) { + return function() { + var args = [value]; + if (arguments.length) { + push.apply(args, arguments); + } + return wrapper.apply(this, args); + }; + } /*--------------------------------------------------------------------------*/ /** - * Escapes a string for inclusion in HTML, replacing `&`, `<`, `"`, and `'` - * characters. + * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their + * corresponding HTML entities. * * @static * @memberOf _ @@ -2992,8 +3691,8 @@ * @returns {String} Returns the escaped string. * @example * - * _.escape('Curly, Larry & Moe'); - * // => "Curly, Larry & Moe" + * _.escape('Moe, Larry & Curly'); + * // => "Moe, Larry & Curly" */ function escape(string) { return string == null ? '' : (string + '').replace(reUnescapedHtml, escapeHtmlChar); @@ -3001,6 +3700,7 @@ /** * This function returns the first argument passed to it. + * * Note: It is used throughout Lo-Dash as a default callback. * * @static @@ -3034,11 +3734,11 @@ * } * }); * - * _.capitalize('curly'); - * // => 'Curly' - * - * _('larry').capitalize(); + * _.capitalize('larry'); * // => 'Larry' + * + * _('curly').capitalize(); + * // => 'Curly' */ function mixin(object) { forEach(functions(object), function(methodName) { @@ -3110,13 +3810,20 @@ return null; } var value = object[property]; - return toString.call(value) == funcClass ? object[property]() : value; + 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 + * + * Note: Lo-Dash may be used in Chrome extensions by either creating a `lodash csp` + * build and avoiding `_.template` use, or loading Lo-Dash in a sandboxed page. + * See http://developer.chrome.com/trunk/extensions/sandboxingEval.html + * * @static * @memberOf _ * @category Utilities @@ -3127,41 +3834,47 @@ * is given, else it returns the interpolated text. * @example * - * // using compiled template + * // 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', 'curly', 'larry'] }); - * // => '
  • moe
  • curly
  • larry
  • ' + * _.template(list, { 'people': ['moe', 'larry', 'curly'] }); + * // => '
  • moe
  • larry
  • curly
  • ' * - * var template = _.template('<%- value %>'); - * template({ 'value': ' + * // using the `source` property to inline compiled templates for meaningful + * // line numbers in error messages and a stack trace + * fs.writeFileSync(path.join(cwd, 'jst.js'), '\ + * var JST = {\ + * "main": ' + _.template(mainText).source + '\ + * };\ + * '); */ function template(text, data, options) { // based on John Resig's `tmpl` implementation @@ -3169,25 +3882,28 @@ // and Laura Doktorova's doT.js // https://github.com/olado/doT options || (options = {}); + text += ''; var isEvaluating, - isInterpolating, result, - defaults = lodash.templateSettings, escapeDelimiter = options.escape, evaluateDelimiter = options.evaluate, interpolateDelimiter = options.interpolate, - variable = options.variable; + settings = lodash.templateSettings, + variable = options.variable || settings.variable, + hasVariable = variable; - // use template defaults if no option is provided + // use default settings if no options object is provided if (escapeDelimiter == null) { - escapeDelimiter = defaults.escape; + escapeDelimiter = settings.escape; } if (evaluateDelimiter == null) { - evaluateDelimiter = defaults.evaluate; + // use `false` as the fallback value, instead of leaving it `undefined`, + // so the initial assignment of `reEvaluateDelimiter` will still occur + evaluateDelimiter = settings.evaluate || false; } if (interpolateDelimiter == null) { - interpolateDelimiter = defaults.interpolate; + interpolateDelimiter = settings.interpolate; } // tokenize delimiters to avoid escaping them @@ -3195,37 +3911,66 @@ text = text.replace(escapeDelimiter, tokenizeEscape); } if (interpolateDelimiter) { - isInterpolating = text != (text = text.replace(interpolateDelimiter, tokenizeInterpolate)); + text = text.replace(interpolateDelimiter, tokenizeInterpolate); } - if (evaluateDelimiter) { - isEvaluating = text != (text = text.replace(evaluateDelimiter, tokenizeEvaluate)); + if (evaluateDelimiter != lastEvaluateDelimiter) { + // generate `reEvaluateDelimiter` to match `_.templateSettings.evaluate` + // and internal ``, `` delimiters + lastEvaluateDelimiter = evaluateDelimiter; + reEvaluateDelimiter = RegExp( + '|' + + (evaluateDelimiter ? '|' + evaluateDelimiter.source : '') + , 'g'); } + isEvaluating = tokenized.length; + text = text.replace(reEvaluateDelimiter, tokenizeEvaluate); + isEvaluating = isEvaluating != tokenized.length; // escape characters that cannot be included in string literals and // detokenize delimiter code snippets - text = "__p='" + text + text = "__p += '" + text .replace(reUnescapedString, escapeStringChar) .replace(reToken, detokenize) + "';\n"; // clear stored code snippets tokenized.length = 0; - // if `options.variable` is not specified, add `data` to the top of the scope chain - if (!variable) { - variable = defaults.variable; - text = 'with (' + variable + ' || {}) {\n' + text + '\n}\n'; + // if `variable` is not specified and the template contains "evaluate" + // delimiters, wrap a with-statement around the generated code to add the + // data object to the top of the scope chain + if (!hasVariable) { + variable = lastVariable || 'obj'; + + if (isEvaluating) { + text = 'with (' + variable + ') {\n' + text + '\n}\n'; + } + else { + if (variable != lastVariable) { + // generate `reDoubleVariable` to match references like `obj.obj` inside + // transformed "escape" and "interpolate" delimiters + lastVariable = variable; + reDoubleVariable = RegExp('(\\(\\s*)' + variable + '\\.' + variable + '\\b', 'g'); + } + // avoid a with-statement by prepending data object references to property names + text = text + .replace(reInsertVariable, '$&' + variable + '.') + .replace(reDoubleVariable, '$1__d'); + } } + // cleanup code by stripping empty strings + text = ( isEvaluating ? text.replace(reEmptyStringLeading, '') : text) + .replace(reEmptyStringMiddle, '$1') + .replace(reEmptyStringTrailing, '$1;'); + + // frame code as the function body text = 'function(' + variable + ') {\n' + - 'var __p' + - (isInterpolating - ? ', __t' - : '' - ) + + (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') + + 'var __t, __p = \'\', __e = _.escape' + (isEvaluating ? ', __j = Array.prototype.join;\n' + 'function print() { __p += __j.call(arguments, \'\') }\n' - : ';\n' + : (hasVariable ? '' : ', __d = ' + variable + '.' + variable + ' || ' + variable) + ';\n' ) + text + 'return __p\n}'; @@ -3236,14 +3981,21 @@ text += '\n//@ sourceURL=/lodash/template/source[' + (templateCounter++) + ']'; } - result = Function('_', 'return ' + text)(lodash); + try { + result = Function('_', 'return ' + text)(lodash); + } catch(e) { + // defer syntax errors until the compiled template is executed to allow + // examining the `source` property beforehand and for consistency, + // because other template related errors occur at execution + result = function() { throw e; }; + } if (data) { return result(data); } - // provide the compiled function's source via its `toString()` method, in + // provide the compiled function's source via its `toString` method, in // supported environments, or the `source` property as a convenience for - // build time precompilation + // inlining compiled templates during the build process result.source = text; return result; } @@ -3279,6 +4031,24 @@ } } + /** + * Converts the HTML entities `&`, `<`, `>`, `"`, and `'` + * in `string` to their corresponding characters. + * + * @static + * @memberOf _ + * @category Utilities + * @param {String} string The string to unescape. + * @returns {String} Returns the unescaped string. + * @example + * + * _.unescape('Moe, Larry & Curly'); + * // => "Moe, Larry & Curly" + */ + function unescape(string) { + return string == null ? '' : (string + '').replace(reEscapedHtml, unescapeHtmlChar); + } + /** * Generates a unique id. If `prefix` is passed, the id will be appended to it. * @@ -3337,7 +4107,7 @@ * @static * @memberOf _ * @category Chaining - * @param {Mixed} value The value to pass to `callback`. + * @param {Mixed} value The value to pass to `interceptor`. * @param {Function} interceptor The function to invoke. * @returns {Mixed} Returns `value`. * @example @@ -3398,7 +4168,7 @@ * @memberOf _ * @type String */ - lodash.VERSION = '0.3.2'; + lodash.VERSION = '0.6.1'; // assign static methods lodash.after = after; @@ -3409,11 +4179,13 @@ lodash.compact = compact; lodash.compose = compose; lodash.contains = contains; + lodash.countBy = countBy; lodash.debounce = debounce; lodash.defaults = defaults; lodash.defer = defer; lodash.delay = delay; lodash.difference = difference; + lodash.drop = drop; lodash.escape = escape; lodash.every = every; lodash.extend = extend; @@ -3454,6 +4226,7 @@ lodash.map = map; lodash.max = max; lodash.memoize = memoize; + lodash.merge = merge; lodash.min = min; lodash.mixin = mixin; lodash.noConflict = noConflict; @@ -3477,13 +4250,16 @@ lodash.throttle = throttle; lodash.times = times; lodash.toArray = toArray; + lodash.unescape = unescape; lodash.union = union; lodash.uniq = uniq; lodash.uniqueId = uniqueId; lodash.values = values; + lodash.where = where; lodash.without = without; lodash.wrap = wrap; lodash.zip = zip; + lodash.zipObject = zipObject; // assign aliases lodash.all = every; @@ -3497,6 +4273,7 @@ lodash.include = contains; lodash.inject = reduce; lodash.methods = functions; + lodash.omit = drop; lodash.select = filter; lodash.tail = rest; lodash.take = first; @@ -3527,12 +4304,9 @@ var value = this._wrapped; func.apply(value, arguments); - // IE compatibility mode and IE < 9 have buggy Array `shift()` and `splice()` - // functions that fail to remove the last element, `value[0]`, of - // array-like objects even though the `length` property is set to `0`. - // The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` - // is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. - if (value.length === 0) { + // avoid array-like object bugs with `Array#shift` and `Array#splice` in + // Firefox < 10 and IE < 9 + if (hasObjectSpliceBug && value.length === 0) { delete value[0]; } if (this._chain) { diff --git a/package.json b/package.json index 88cc052..85975eb 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ }, "dependencies": { - "lodash": "0.3.2", + "lodash": "0.6.1", "moment": "1.6.2", "underscore.deferred": "0.1.3", "request": "2.9.153" From 678c4dcd0b0b50dc4e837e6d243d4f7ff905abcc Mon Sep 17 00:00:00 2001 From: Irene Ros Date: Mon, 3 Sep 2012 17:28:43 +0200 Subject: [PATCH 04/12] Upgrading moment.js --- lib/moment.js | 626 ++++++++++++++++++++++++++++++++------------------ package.json | 2 +- 2 files changed, 408 insertions(+), 220 deletions(-) diff --git a/lib/moment.js b/lib/moment.js index df6668c..becad68 100644 --- a/lib/moment.js +++ b/lib/moment.js @@ -1,29 +1,38 @@ // moment.js -// version : 1.6.2 +// version : 1.7.0 // author : Tim Wood // license : MIT // momentjs.com (function (Date, undefined) { + /************************************ + Constants + ************************************/ + var moment, - VERSION = "1.6.2", + VERSION = "1.7.0", round = Math.round, i, // internal storage for language config files languages = {}, currentLanguage = 'en', // check for nodeJS - hasModule = (typeof module !== 'undefined'), + hasModule = (typeof module !== 'undefined' && module.exports), - // parameters to check for on the lang config - langConfigProperties = 'months|monthsShort|monthsParse|weekdays|weekdaysShort|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'), + // Parameters to check for on the lang config. This list of properties + // will be inherited from English if not provided in a language + // definition. monthsParse is also a lang config property, but it + // cannot be inherited and as such cannot be enumerated here. + langConfigProperties = 'months|monthsShort|weekdays|weekdaysShort|weekdaysMin|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'), // ASP.NET json date format regex aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, // format tokens - formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|dddd?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?|LT|LL?L?L?)/g, + formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?)/g, + localFormattingTokens = /(LT|LL?L?L?)/g, + formattingRemoveEscapes = /(^\[)|(\\)|\]$/g, // parsing tokens parseMultipleFormatChunker = /([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi, @@ -32,7 +41,7 @@ parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 parseTokenThreeDigits = /\d{3}/, // 000 - 999 - parseTokenFourDigits = /\d{4}/, // 0000 - 9999 + parseTokenFourDigits = /\d{1,4}/, // 0 - 9999 parseTokenWord = /[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i, // any word characters or numbers parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z parseTokenT = /T/i, // T (ISO seperator) @@ -63,20 +72,77 @@ 'Days' : 864e5, 'Months' : 2592e6, 'Years' : 31536e6 - }; + }, + + // format function strings + formatFunctions = {}, + + /* + * moment.fn.format uses new Function() to create an inlined formatting function. + * Results are a 3x speed boost + * http://jsperf.com/momentjs-cached-format-functions + * + * These strings are appended into a function using replaceFormatTokens and makeFormatFunction + */ + formatFunctionStrings = { + // a = placeholder + // b = placeholder + // t = the current moment being formatted + // v = getValueAtKey function + // o = language.ordinal function + // p = leftZeroFill function + // m = language.meridiem value or function + M : '(a=t.month()+1)', + MMM : 'v("monthsShort",t.month())', + MMMM : 'v("months",t.month())', + D : '(a=t.date())', + DDD : '(a=new Date(t.year(),t.month(),t.date()),b=new Date(t.year(),0,1),a=~~(((a-b)/864e5)+1.5))', + d : '(a=t.day())', + dd : 'v("weekdaysMin",t.day())', + ddd : 'v("weekdaysShort",t.day())', + dddd : 'v("weekdays",t.day())', + w : '(a=new Date(t.year(),t.month(),t.date()-t.day()+5),b=new Date(a.getFullYear(),0,4),a=~~((a-b)/864e5/7+1.5))', + YY : 'p(t.year()%100,2)', + YYYY : 'p(t.year(),4)', + a : 'm(t.hours(),t.minutes(),!0)', + A : 'm(t.hours(),t.minutes(),!1)', + H : 't.hours()', + h : 't.hours()%12||12', + m : 't.minutes()', + s : 't.seconds()', + S : '~~(t.milliseconds()/100)', + SS : 'p(~~(t.milliseconds()/10),2)', + SSS : 'p(t.milliseconds(),3)', + Z : '((a=-t.zone())<0?((a=-a),"-"):"+")+p(~~(a/60),2)+":"+p(~~a%60,2)', + ZZ : '((a=-t.zone())<0?((a=-a),"-"):"+")+p(~~(10*a/6),4)' + }, + + ordinalizeTokens = 'DDD w M D d'.split(' '), + paddedTokens = 'M D H h m s w'.split(' '); + + while (ordinalizeTokens.length) { + i = ordinalizeTokens.pop(); + formatFunctionStrings[i + 'o'] = formatFunctionStrings[i] + '+o(a)'; + } + while (paddedTokens.length) { + i = paddedTokens.pop(); + formatFunctionStrings[i + i] = 'p(' + formatFunctionStrings[i] + ',2)'; + } + formatFunctionStrings.DDDD = 'p(' + formatFunctionStrings.DDD + ',3)'; + + + /************************************ + Constructors + ************************************/ + // Moment prototype object - function Moment(date, isUTC) { + function Moment(date, isUTC, lang) { this._d = date; this._isUTC = !!isUTC; - } - - function absRound(number) { - if (number < 0) { - return Math.ceil(number); - } else { - return Math.floor(number); - } + this._a = date._a || null; + date._a = null; + this._lang = lang || false; } // Duration Constructor @@ -129,6 +195,22 @@ years += absRound(months / 12); data.years = years; + + this._lang = false; + } + + + /************************************ + Helpers + ************************************/ + + + function absRound(number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } } // left zero fill a number @@ -167,141 +249,152 @@ return Object.prototype.toString.call(input) === '[object Array]'; } + // compare two arrays, return the number of differences + function compareArrays(array1, array2) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if (~~array1[i] !== ~~array2[i]) { + diffs++; + } + } + return diffs + lengthDiff; + } + // convert an array to a date. // the array should mirror the parameters below // note: all values past the year are optional and will default to the lowest possible value. // [year, month, day , hour, minute, second, millisecond] - function dateFromArray(input) { - return new Date(input[0], input[1] || 0, input[2] || 1, input[3] || 0, input[4] || 0, input[5] || 0, input[6] || 0); + function dateFromArray(input, asUTC) { + var i, date; + for (i = 1; i < 7; i++) { + input[i] = (input[i] == null) ? (i === 2 ? 1 : 0) : input[i]; + } + // we store whether we used utc or not in the input array + input[7] = asUTC; + date = new Date(0); + if (asUTC) { + date.setUTCFullYear(input[0], input[1], input[2]); + date.setUTCHours(input[3], input[4], input[5], input[6]); + } else { + date.setFullYear(input[0], input[1], input[2]); + date.setHours(input[3], input[4], input[5], input[6]); + } + date._a = input; + return date; + } + + // Loads a language definition into the `languages` cache. The function + // takes a key and optionally values. If not in the browser and no values + // are provided, it will load the language file module. As a convenience, + // this function also returns the language values. + function loadLang(key, values) { + var i, m, + parse = []; + + if (!values && hasModule) { + values = require('./lang/' + key); + } + + for (i = 0; i < langConfigProperties.length; i++) { + // If a language definition does not provide a value, inherit + // from English + values[langConfigProperties[i]] = values[langConfigProperties[i]] || + languages.en[langConfigProperties[i]]; + } + + for (i = 0; i < 12; i++) { + m = moment([2000, i]); + parse[i] = new RegExp('^' + (values.months[i] || values.months(m, '')) + + '|^' + (values.monthsShort[i] || values.monthsShort(m, '')).replace('.', ''), 'i'); + } + values.monthsParse = values.monthsParse || parse; + + languages[key] = values; + + return values; + } + + // Determines which language definition to use and returns it. + // + // With no parameters, it will return the global language. If you + // pass in a language key, such as 'en', it will return the + // definition for 'en', so long as 'en' has already been loaded using + // moment.lang. If you pass in a moment or duration instance, it + // will decide the language based on that, or default to the global + // language. + function getLangDefinition(m) { + var langKey = (typeof m === 'string') && m || + m && m._lang || + null; + + return langKey ? (languages[langKey] || loadLang(langKey)) : moment; + } + + + /************************************ + Formatting + ************************************/ + + + // helper for building inline formatting functions + function replaceFormatTokens(token) { + return formatFunctionStrings[token] ? + ("'+(" + formatFunctionStrings[token] + ")+'") : + token.replace(formattingRemoveEscapes, "").replace(/\\?'/g, "\\'"); + } + + // helper for recursing long date formatting tokens + function replaceLongDateFormatTokens(input) { + return getLangDefinition().longDateFormat[input] || input; + } + + function makeFormatFunction(format) { + var output = "var a,b;return '" + + format.replace(formattingTokens, replaceFormatTokens) + "';", + Fn = Function; // get around jshint + // t = the current moment being formatted + // v = getValueAtKey function + // o = language.ordinal function + // p = leftZeroFill function + // m = language.meridiem value or function + return new Fn('t', 'v', 'o', 'p', 'm', output); + } + + function makeOrGetFormatFunction(format) { + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } + return formatFunctions[format]; } // format date using native date object - function formatMoment(m, inputString) { - var currentMonth = m.month(), - currentDate = m.date(), - currentYear = m.year(), - currentDay = m.day(), - currentHours = m.hours(), - currentMinutes = m.minutes(), - currentSeconds = m.seconds(), - currentMilliseconds = m.milliseconds(), - currentZone = -m.zone(), - ordinal = moment.ordinal, - meridiem = moment.meridiem; - // check if the character is a format - // return formatted string or non string. - // - // uses switch/case instead of an object of named functions (like http://phpjs.org/functions/date:380) - // for minification and performance - // see http://jsperf.com/object-of-functions-vs-switch for performance comparison - function replaceFunction(input) { - // create a couple variables to be used later inside one of the cases. - var a, b; - switch (input) { - // MONTH - case 'M' : - return currentMonth + 1; - case 'Mo' : - return (currentMonth + 1) + ordinal(currentMonth + 1); - case 'MM' : - return leftZeroFill(currentMonth + 1, 2); - case 'MMM' : - return moment.monthsShort[currentMonth]; - case 'MMMM' : - return moment.months[currentMonth]; - // DAY OF MONTH - case 'D' : - return currentDate; - case 'Do' : - return currentDate + ordinal(currentDate); - case 'DD' : - return leftZeroFill(currentDate, 2); - // DAY OF YEAR - case 'DDD' : - a = new Date(currentYear, currentMonth, currentDate); - b = new Date(currentYear, 0, 1); - return ~~ (((a - b) / 864e5) + 1.5); - case 'DDDo' : - a = replaceFunction('DDD'); - return a + ordinal(a); - case 'DDDD' : - return leftZeroFill(replaceFunction('DDD'), 3); - // WEEKDAY - case 'd' : - return currentDay; - case 'do' : - return currentDay + ordinal(currentDay); - case 'ddd' : - return moment.weekdaysShort[currentDay]; - case 'dddd' : - return moment.weekdays[currentDay]; - // WEEK OF YEAR - case 'w' : - a = new Date(currentYear, currentMonth, currentDate - currentDay + 5); - b = new Date(a.getFullYear(), 0, 4); - return ~~ ((a - b) / 864e5 / 7 + 1.5); - case 'wo' : - a = replaceFunction('w'); - return a + ordinal(a); - case 'ww' : - return leftZeroFill(replaceFunction('w'), 2); - // YEAR - case 'YY' : - return leftZeroFill(currentYear % 100, 2); - case 'YYYY' : - return currentYear; - // AM / PM - case 'a' : - return meridiem ? meridiem(currentHours, currentMinutes, false) : (currentHours > 11 ? 'pm' : 'am'); - case 'A' : - return meridiem ? meridiem(currentHours, currentMinutes, true) : (currentHours > 11 ? 'PM' : 'AM'); - // 24 HOUR - case 'H' : - return currentHours; - case 'HH' : - return leftZeroFill(currentHours, 2); - // 12 HOUR - case 'h' : - return currentHours % 12 || 12; - case 'hh' : - return leftZeroFill(currentHours % 12 || 12, 2); - // MINUTE - case 'm' : - return currentMinutes; - case 'mm' : - return leftZeroFill(currentMinutes, 2); - // SECOND - case 's' : - return currentSeconds; - case 'ss' : - return leftZeroFill(currentSeconds, 2); - // MILLISECONDS - case 'S' : - return ~~ (currentMilliseconds / 100); - case 'SS' : - return leftZeroFill(~~(currentMilliseconds / 10), 2); - case 'SSS' : - return leftZeroFill(currentMilliseconds, 3); - // TIMEZONE - case 'Z' : - return (currentZone < 0 ? '-' : '+') + leftZeroFill(~~(Math.abs(currentZone) / 60), 2) + ':' + leftZeroFill(~~(Math.abs(currentZone) % 60), 2); - case 'ZZ' : - return (currentZone < 0 ? '-' : '+') + leftZeroFill(~~(10 * Math.abs(currentZone) / 6), 4); - // LONG DATES - case 'L' : - case 'LL' : - case 'LLL' : - case 'LLLL' : - case 'LT' : - return formatMoment(m, moment.longDateFormat[input]); - // DEFAULT - default : - return input.replace(/(^\[)|(\\)|\]$/g, ""); - } + function formatMoment(m, format) { + var lang = getLangDefinition(m); + + function getValueFromArray(key, index) { + return lang[key].call ? lang[key](m, format) : lang[key][index]; + } + + while (localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + } + + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); } - return inputString.replace(formattingTokens, replaceFunction); + + return formatFunctions[format](m, getValueFromArray, lang.ordinal, leftZeroFill, lang.meridiem); } + + /************************************ + Parsing + ************************************/ + + // get the regex to find the next token function getParseRegexForToken(token) { switch (token) { @@ -316,6 +409,7 @@ return parseTokenOneToThreeDigits; case 'MMM': case 'MMMM': + case 'dd': case 'ddd': case 'dddd': case 'a': @@ -328,7 +422,6 @@ return parseTokenT; case 'MM': case 'DD': - case 'dd': case 'YY': case 'HH': case 'hh': @@ -360,7 +453,7 @@ case 'MMM' : // fall through to MMMM case 'MMMM' : for (a = 0; a < 12; a++) { - if (moment.monthsParse[a].test(input)) { + if (getLangDefinition().monthsParse[a].test(input)) { datePartArray[1] = a; break; } @@ -371,7 +464,9 @@ case 'DD' : // fall through to DDDD case 'DDD' : // fall through to DDDD case 'DDDD' : - datePartArray[2] = ~~input; + if (input != null) { + datePartArray[2] = ~~input; + } break; // YEAR case 'YY' : @@ -456,21 +551,7 @@ datePartArray[3] += config.tzh; datePartArray[4] += config.tzm; // return - return config.isUTC ? new Date(Date.UTC.apply({}, datePartArray)) : dateFromArray(datePartArray); - } - - // compare two arrays, return the number of differences - function compareArrays(array1, array2) { - var len = Math.min(array1.length, array2.length), - lengthDiff = Math.abs(array1.length - array2.length), - diffs = 0, - i; - for (i = 0; i < len; i++) { - if (~~array1[i] !== ~~array2[i]) { - diffs++; - } - } - return diffs + lengthDiff; + return dateFromArray(datePartArray, config.isUTC); } // date from string and array of format strings @@ -512,15 +593,21 @@ return new Date(string); } + + /************************************ + Relative Time + ************************************/ + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture) { - var rt = moment.relativeTime[string]; + function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { + var rt = lang.relativeTime[string]; return (typeof rt === 'function') ? rt(number || 1, !!withoutSuffix, string, isFuture) : rt.replace(/%d/i, number || 1); } - function relativeTime(milliseconds, withoutSuffix) { + function relativeTime(milliseconds, withoutSuffix, lang) { var seconds = round(Math.abs(milliseconds) / 1000), minutes = round(seconds / 60), hours = round(minutes / 60), @@ -538,20 +625,25 @@ years === 1 && ['y'] || ['yy', years]; args[2] = withoutSuffix; args[3] = milliseconds > 0; + args[4] = lang; return substituteTimeAgo.apply({}, args); } + + /************************************ + Top Level Functions + ************************************/ + + moment = function (input, format) { if (input === null || input === '') { return null; } var date, - matched, - isUTC; + matched; // parse Moment object if (moment.isMoment(input)) { - date = new Date(+input._d); - isUTC = input._isUTC; + return new Moment(new Date(+input._d), input._isUTC, input._lang); // parse string and format } else if (format) { if (isArray(format)) { @@ -569,17 +661,23 @@ typeof input === 'string' ? makeDateFromString(input) : new Date(input); } - return new Moment(date, isUTC); + + return new Moment(date); }; // creating with utc moment.utc = function (input, format) { if (isArray(input)) { - return new Moment(new Date(Date.UTC.apply({}, input)), true); + return new Moment(dateFromArray(input, true), true); + } + // if we don't have a timezone, we need to add one to trigger parsing into utc + if (typeof input === 'string' && !parseTokenTimezone.exec(input)) { + input += ' +0000'; + if (format) { + format += ' Z'; + } } - return (format && input) ? - moment(input + ' +0000', format + ' Z').utc() : - moment(input && !parseTokenTimezone.exec(input) ? input + '+0000' : input).utc(); + return moment(input, format).utc(); }; // creating with unix timestamp (in seconds) @@ -591,7 +689,8 @@ moment.duration = function (input, key) { var isDuration = moment.isDuration(input), isNumber = (typeof input === 'number'), - duration = (isDuration ? input._data : (isNumber ? {} : input)); + duration = (isDuration ? input._data : (isNumber ? {} : input)), + ret; if (isNumber) { if (key) { @@ -601,7 +700,13 @@ } } - return new Duration(duration); + ret = new Duration(duration); + + if (isDuration) { + ret._lang = input._lang; + } + + return ret; }; // humanizeDuration @@ -617,40 +722,49 @@ // default format moment.defaultFormat = isoFormat; - // language switching and caching + // This function will load languages and then set the global language. If + // no arguments are passed in, it will simply return the current global + // language key. moment.lang = function (key, values) { - var i, req, - parse = []; + var i; + if (!key) { return currentLanguage; } - if (values) { - for (i = 0; i < 12; i++) { - parse[i] = new RegExp('^' + values.months[i] + '|^' + values.monthsShort[i].replace('.', ''), 'i'); - } - values.monthsParse = values.monthsParse || parse; - languages[key] = values; + if (values || !languages[key]) { + loadLang(key, values); } if (languages[key]) { + // deprecated, to get the language definition variables, use the + // moment.fn.lang method or the getLangDefinition function. for (i = 0; i < langConfigProperties.length; i++) { - moment[langConfigProperties[i]] = languages[key][langConfigProperties[i]] || - languages.en[langConfigProperties[i]]; + moment[langConfigProperties[i]] = languages[key][langConfigProperties[i]]; } + moment.monthsParse = languages[key].monthsParse; currentLanguage = key; - } else { - if (hasModule) { - req = require('./lang/' + key); - moment.lang(key, req); - } } }; - // set default language + // returns language data + moment.langData = getLangDefinition; + + // compare moment object + moment.isMoment = function (obj) { + return obj instanceof Moment; + }; + + // for typechecking Duration objects + moment.isDuration = function (obj) { + return obj instanceof Duration; + }; + + // Set default language, other languages will inherit from English. moment.lang('en', { months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), longDateFormat : { LT : "h:mm A", L : "MM/DD/YYYY", @@ -658,7 +772,13 @@ LLL : "MMMM D YYYY LT", LLLL : "dddd, MMMM D YYYY LT" }, - meridiem : false, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + }, calendar : { sameDay : '[Today at] LT', nextDay : '[Tomorrow at] LT', @@ -691,17 +811,12 @@ } }); - // compare moment object - moment.isMoment = function (obj) { - return obj instanceof Moment; - }; - // for typechecking Duration objects - moment.isDuration = function (obj) { - return obj instanceof Duration; - }; + /************************************ + Moment Prototype + ************************************/ + - // shortcut for prototype moment.fn = Moment.prototype = { clone : function () { @@ -724,6 +839,27 @@ return this._d; }, + toArray : function () { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hours(), + m.minutes(), + m.seconds(), + m.milliseconds(), + !!this._isUTC + ]; + }, + + isValid : function () { + if (this._a) { + return !compareArrays(this._a, (this._a[7] ? moment.utc(this) : this).toArray()); + } + return !isNaN(this._d.getTime()); + }, + utc : function () { this._isUTC = true; return this; @@ -774,7 +910,7 @@ }, from : function (time, withoutSuffix) { - return moment.duration(this.diff(time)).humanize(!withoutSuffix); + return moment.duration(this.diff(time)).lang(this._lang).humanize(!withoutSuffix); }, fromNow : function (withoutSuffix) { @@ -783,7 +919,7 @@ calendar : function () { var diff = this.diff(moment().sod(), 'days', true), - calendar = moment.calendar, + calendar = this.lang().calendar, allElse = calendar.sameElse, format = diff < -6 ? allElse : diff < -1 ? calendar.lastWeek : @@ -810,20 +946,43 @@ this.add({ d : input - day }); }, + startOf: function (val) { + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (val.replace(/s$/, '')) { + case 'year': + this.month(0); + /* falls through */ + case 'month': + this.date(1); + /* falls through */ + case 'day': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + /* falls through */ + } + return this; + }, + + endOf: function (val) { + return this.startOf(val).add(val.replace(/s?$/, 's'), 1).subtract('ms', 1); + }, + sod: function () { - return moment(this) - .hours(0) - .minutes(0) - .seconds(0) - .milliseconds(0); + return this.clone().startOf('day'); }, eod: function () { // end of day = start of day plus 1 day, minus 1 millisecond - return this.sod().add({ - d : 1, - ms : -1 - }); + return this.clone().endOf('day'); }, zone : function () { @@ -831,7 +990,19 @@ }, daysInMonth : function () { - return moment(this).month(this.month() + 1).date(0).date(); + return moment.utc([this.year(), this.month() + 1, 0]).date(); + }, + + // If passed a language key, it will set the language for this + // instance. Otherwise, it will return the language configuration + // variables for this instance. + lang : function (lang) { + if (lang === undefined) { + return getLangDefinition(this); + } else { + this._lang = lang; + return this; + } } }; @@ -856,6 +1027,12 @@ // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') makeGetterAndSetter('year', 'FullYear'); + + /************************************ + Duration Prototype + ************************************/ + + moment.duration.fn = Duration.prototype = { weeks : function () { return absRound(this.days() / 7); @@ -869,15 +1046,17 @@ humanize : function (withSuffix) { var difference = +this, - rel = moment.relativeTime, - output = relativeTime(difference, !withSuffix); + rel = this.lang().relativeTime, + output = relativeTime(difference, !withSuffix, this.lang()); if (withSuffix) { output = (difference <= 0 ? rel.past : rel.future).replace(/%s/i, output); } return output; - } + }, + + lang : moment.fn.lang }; function makeDurationGetter(name) { @@ -901,13 +1080,22 @@ makeDurationAsGetter('Weeks', 6048e5); + + /************************************ + Exposing Moment + ************************************/ + + // CommonJS module is defined if (hasModule) { module.exports = moment; } /*global ender:false */ - if (typeof window !== 'undefined' && typeof ender === 'undefined') { - window.moment = moment; + if (typeof ender === 'undefined') { + // here, `this` means `window` in the browser, or `global` on the server + // add `moment` as a global object via a string identifier, + // for Closure Compiler "advanced" mode + this['moment'] = moment; } /*global define:false */ if (typeof define === "function" && define.amd) { @@ -915,4 +1103,4 @@ return moment; }); } -})(Date); \ No newline at end of file +}).call(this, Date); \ No newline at end of file diff --git a/package.json b/package.json index 85975eb..ff8f618 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "dependencies": { "lodash": "0.6.1", - "moment": "1.6.2", + "moment": "1.7.0", "underscore.deferred": "0.1.3", "request": "2.9.153" } From fe24ea3d9f5a7b1eb391215f3ce92da6e8de8d80 Mon Sep 17 00:00:00 2001 From: Irene Ros Date: Mon, 3 Sep 2012 17:37:35 +0200 Subject: [PATCH 05/12] Version 0.2.2 release --- README.md | 19 +- dist/LASTBUILD | 2 +- dist/{miso.ds.0.2.1.js => miso.ds.0.2.2.js} | 219 +- ...ds.deps.0.2.1.js => miso.ds.deps.0.2.2.js} | 5493 ++++++++++------- ...s.ie.0.2.1.js => miso.ds.deps.ie.0.2.2.js} | 5402 +++++++++------- dist/miso.ds.deps.ie.min.0.2.1.js | 9 - dist/miso.ds.deps.ie.min.0.2.2.js | 9 + dist/miso.ds.deps.min.0.2.1.js | 9 - dist/miso.ds.deps.min.0.2.2.js | 9 + dist/miso.ds.dev.0.2.1.zip | Bin 65884 -> 0 bytes dist/miso.ds.dev.0.2.2.zip | Bin 0 -> 74955 bytes dist/miso.ds.min.0.2.1.js | 9 - dist/miso.ds.min.0.2.2.js | 9 + ...ds.deps.0.2.1.js => miso.ds.deps.0.2.2.js} | 219 +- package.json | 4 +- 15 files changed, 6916 insertions(+), 4496 deletions(-) rename dist/{miso.ds.0.2.1.js => miso.ds.0.2.2.js} (94%) rename dist/{miso.ds.deps.0.2.1.js => miso.ds.deps.0.2.2.js} (73%) rename dist/{miso.ds.deps.ie.0.2.1.js => miso.ds.deps.ie.0.2.2.js} (75%) delete mode 100644 dist/miso.ds.deps.ie.min.0.2.1.js create mode 100644 dist/miso.ds.deps.ie.min.0.2.2.js delete mode 100644 dist/miso.ds.deps.min.0.2.1.js create mode 100644 dist/miso.ds.deps.min.0.2.2.js delete mode 100644 dist/miso.ds.dev.0.2.1.zip create mode 100644 dist/miso.ds.dev.0.2.2.zip delete mode 100644 dist/miso.ds.min.0.2.1.js create mode 100644 dist/miso.ds.min.0.2.2.js rename dist/node/{miso.ds.deps.0.2.1.js => miso.ds.deps.0.2.2.js} (94%) diff --git a/README.md b/README.md index fec7923..cf1a4b4 100644 --- a/README.md +++ b/README.md @@ -9,26 +9,26 @@ Read more about Dataset [here](http://misoproject.com/dataset) ### Including Dependencies -[miso.ds.dev.zip](https://github.com/downloads/misoproject/dataset/miso.ds.dev.0.2.1.zip) - Download Development With Dependencies - 0.2.1 +[miso.ds.dev.zip](https://github.com/downloads/misoproject/dataset/miso.ds.dev.0.2.2.zip) - Download Development With Dependencies - 0.2.2 -[miso.ds.deps.min.js](https://github.com/downloads/misoproject/dataset/miso.ds.deps.min.0.2.1.js) - Download Production With Dependencies - 0.2.1 +[miso.ds.deps.min.js](https://github.com/downloads/misoproject/dataset/miso.ds.deps.min.0.2.2.js) - Download Production With Dependencies - 0.2.2 ### Without Dependencies The following builds do not have any of the dependencies built in. It is your own responsibility to include them as appropriate script elements in your page. -[miso.ds.js](https://github.com/downloads/misoproject/dataset/miso.ds.min.0.2.1.js) - Download Production No Dependencies - 0.2.1 +[miso.ds.js](https://github.com/downloads/misoproject/dataset/miso.ds.min.0.2.2.js) - Download Production No Dependencies - 0.2.2 -[miso.ds.min.js](https://github.com/misoproject/dataset/tree/master/dist/) - Download Development No Dependencies - 0.2.1 +[miso.ds.min.js](https://github.com/misoproject/dataset/tree/master/dist/) - Download Development No Dependencies - 0.2.2 ### Dependencies Dataset has the following dependencies: -* [Lodash.js 0.3.2](http://lodash.com/) +* [Lodash.js 0.6.1](http://lodash.com/) * [Underscore.math.js (version unknown)](https://github.com/syntagmatic/underscore.math) * [Underscore.deferred.js 0.1.2](https://github.com/wookiehangover/underscore.Deferred) -* [moment.js 1.4.0](http://momentjs.com/) (for date and time parsing) +* [moment.js 1.7.0](http://momentjs.com/) (for date and time parsing) If you are planning on supporting IE, include the following json2.js library as well: * [json2.js 2011-10-19](https://github.com/douglascrockford/JSON-js) @@ -41,6 +41,13 @@ The full documentation set can be found here: Miso.Dataset works in the browser and in Node.js. +### Have an intersting issue or question? + +Maybe others have as well. Ask your quesiton as a ticket +or check out the current listing of tips and tricks in our +[How do I...](https://github.com/misoproject/dataset/wiki/How-Do-I...) +wiki page. + #### Browser support Include the appropriate libs as script tags in your web pages diff --git a/dist/LASTBUILD b/dist/LASTBUILD index 1a1fc57..b845e2c 100644 --- a/dist/LASTBUILD +++ b/dist/LASTBUILD @@ -1 +1 @@ -2012/06/29 09:18 +2012/09/03 05:58 diff --git a/dist/miso.ds.0.2.1.js b/dist/miso.ds.0.2.2.js similarity index 94% rename from dist/miso.ds.0.2.1.js rename to dist/miso.ds.0.2.2.js index f5e59c6..9808bf4 100644 --- a/dist/miso.ds.0.2.1.js +++ b/dist/miso.ds.0.2.2.js @@ -1,5 +1,5 @@ /** -* Miso.Dataset - v0.2.1 - 6/29/2012 +* Miso.Dataset - v0.2.2 - 9/3/2012 * http://github.com/misoproject/dataset * Copyright (c) 2012 Alex Graul, Irene Ros; * Dual Licensed: MIT, GPL @@ -9,8 +9,7 @@ (function(global, _) { - /* @exports namespace */ - var Miso = global.Miso = {}; + var Miso = global.Miso || (global.Miso = {}); Miso.typeOf = function(value, options) { var types = _.keys(Miso.types), @@ -34,29 +33,37 @@ mixed : { name : 'mixed', coerce : function(v) { + if (_.isNull(v) || typeof v === "undefined" || _.isNaN(v)) { + return null; + } return v; }, test : function(v) { return true; }, compare : function(s1, s2) { - if (s1 < s2) { return -1; } - if (s1 > s2) { return 1; } - return 0; + if ( _.isEqual(s1, s2) ) { return 0; } + if (s1 < s2) { return -1;} + if (s1 > s2) { return 1; } }, numeric : function(v) { - return _.isNaN( Number(v) ) ? null : Number(v); + return v === null || _.isNaN(+v) ? null : +v; } }, string : { name : "string", coerce : function(v) { - return v == null ? null : v.toString(); + if (_.isNaN(v) || v === null || typeof v === "undefined") { + return null; + } + return v.toString(); }, + test : function(v) { return (v === null || typeof v === "undefined" || typeof v === 'string'); }, + compare : function(s1, s2) { if (s1 == null && s2 != null) { return -1; } if (s1 != null && s2 == null) { return 1; } @@ -80,6 +87,9 @@ name : "boolean", regexp : /^(true|false)$/, coerce : function(v) { + if (_.isNaN(v) || v === null || typeof v === "undefined") { + return null; + } if (v === 'false') { return false; } return Boolean(v); }, @@ -98,7 +108,7 @@ return (n1 < n2 ? -1 : 1); }, numeric : function(value) { - if (_.isNaN(value)) { + if (value === null || _.isNaN(value)) { return null; } else { return (value) ? 1 : 0; @@ -108,12 +118,13 @@ number : { name : "number", - regexp : /^[\-\.]?[0-9]+([\.][0-9]+)?$/, + regexp : /^\s*[\-\.]?[0-9]+([\.][0-9]+)?\s*$/, coerce : function(v) { - if (_.isNull(v)) { + var cv = +v; + if (_.isNull(v) || typeof v === "undefined" || _.isNaN(cv)) { return null; } - return _.isNaN(v) ? null : +v; + return cv; }, test : function(v) { if (v === null || typeof v === "undefined" || typeof v === 'number' || this.regexp.test( v ) ) { @@ -130,6 +141,9 @@ return (n1 < n2 ? -1 : 1); }, numeric : function(value) { + if (_.isNaN(value) || value === null) { + return null; + } return value; } }, @@ -179,6 +193,11 @@ coerce : function(v, options) { options = options || {}; + + if (_.isNull(v) || typeof v === "undefined" || _.isNaN(v)) { + return null; + } + // if string, then parse as a time if (_.isString(v)) { var format = options.format || this.format; @@ -211,6 +230,9 @@ return 0; }, numeric : function( value ) { + if (_.isNaN(value) || value === null) { + return null; + } return value.valueOf(); } } @@ -565,6 +587,35 @@ }, this); }, + /** + * If this is a computed column, it calculates the value + * for this column and adds it to the data. + * Parameters: + * row - the row from which column is computed. + * i - Optional. the index at which this value will get added. + * Returns + * val - the computed value + */ + compute : function(row, i) { + if (this.func) { + var val = this.func(row); + if (typeof i !== "undefined") { + this.data[i] = val; + } else { + this.data.push(val); + } + + return val; + } + }, + + /** + * returns true if this is a computed column. False otherwise. + */ + isComputed : function() { + return !_.isUndefined(this.func); + }, + _sum : function() { return _.sum(this.data); }, @@ -994,6 +1045,11 @@ _.each(row, function(value, key) { var column = this.column(key); + // is this a computed column? if so throw an error + if (column.isComputed()) { + throw "You're trying to update a computed column. Those get computed!"; + } + // if we suddenly see values for data that didn't exist before as a column // just drop it. First fetch defines the column structure. if (typeof column !== "undefined") { @@ -1013,18 +1069,28 @@ } else { throw("incorrect value '" + row[column.name] + "' of type " + Miso.typeOf(row[column.name], column) + - " passed to column with type " + column.type); + " passed to column '" + column.name + "' with type " + column.type); } } }, this); + // do we have any computed columns? If so we need to calculate their values. + if (this._computedColumns) { + _.each(this._computedColumns, function(column) { + var newVal = column.compute(row); + row[column.name] = newVal; + }); + } + // if we don't have a comparator, just append them at the end. if (_.isUndefined(this.comparator)) { // add all data _.each(this._columns, function(column) { - column.data.push(!_.isUndefined(row[column.name]) && !_.isNull(row[column.name]) ? row[column.name] : null); + if (!column.isComputed()) { + column.data.push(!_.isUndefined(row[column.name]) && !_.isNull(row[column.name]) ? row[column.name] : null); + } }); this.length++; @@ -1471,6 +1537,7 @@ Version 0.0.1.2 this._columns = []; this._columnPositionByName = {}; + this._computedColumns = []; if (typeof options !== "undefined") { options = options || {}; @@ -1563,8 +1630,6 @@ Version 0.0.1.2 this.uniqueAgainst = options.uniqueAgainst; } - - // if there is no data and no url set, we must be building // the dataset from scratch, so create an id column. if (_.isUndefined(options.data) && _.isUndefined(options.url)) { @@ -1671,43 +1736,33 @@ Version 0.0.1.2 againstColumn : function(data) { var rows = [], - colNames = _.keys(data), row, - // get against unique col - uniqCol = this.column(this.uniqueAgainst), - len = data[this._columns[1].name].length, - dataLength = _.max(_.map(colNames, function(name) { - return data[name].length; - }, this)); - - var posToRemove = [], i; - for(i = 0; i < len; i++) { - - var datum = data[this.uniqueAgainst][i]; - // this is a non unique row, remove it from all the data - // arrays - if (uniqCol.data.indexOf(datum) !== -1) { - posToRemove.push(i); - } - } - - // sort and reverse the removal ids, this way we won't - // lose position by removing an early id that will shift - // array and throw all other ids off. - posToRemove.sort().reverse(); + uniqName = this.uniqueAgainst, + uniqCol = this.column(uniqName), + toAdd = [], + toUpdate = [], + toRemove = []; + + _.each(data[uniqName], function(key, dataIndex) { + var rowIndex = uniqCol.data.indexOf( Miso.types[uniqCol.type].coerce(key) ); + + var row = {}; + _.each(data, function(col, name) { + row[name] = col[dataIndex]; + }); - for(i = 0; i < dataLength; i++) { - if (posToRemove.indexOf(i) === -1) { - row = {}; - for(var j = 0; j < colNames.length; j++) { - row[colNames[j]] = data[colNames[j]][i]; - } - rows.push(row); + if (rowIndex === -1) { + toAdd.push( row ); + } else { + toUpdate.push( row ); + var oldRow = this.rowById(this.column('_id').data[rowIndex])._id; + this.update(oldRow, row); } + }, this); + if (toAdd.length > 0) { + this.add(toAdd); } - - this.add(rows); }, //Always blindly add new rows @@ -1790,6 +1845,46 @@ Version 0.0.1.2 }, this); }, + /** + * Allows adding of a computed column. A computed column is + * a column that is somehow based on the other existing columns. + * Parameters: + * name : name of new column + * type : The type of the column based on existing types. + * func : The way that the column is derived. It takes a row as a parameter. + */ + addComputedColumn : function(name, type, func) { + // check if we already ahve a column by this name. + if ( !_.isUndefined(this.column(name)) ) { + throw "There is already a column by this name."; + } else { + + // check that this is a known type. + if (typeof Miso.types[type] === "undefined") { + throw "The type " + type + " doesn't exist"; + } + + var column = new Miso.Column({ + name : name, + type : type, + func : _.bind(func, this) + }); + + this._columns.push(column); + this._computedColumns.push(column); + this._columnPositionByName[column.name] = this._columns.length - 1; + + // do we already have data? if so compute the values for this column. + if (this.length > 0) { + this.each(function(row, i) { + column.compute(row, i); + }, this); + } + + return column; + } + }, + /** * Adds a single column to the dataset * Parameters: @@ -1953,8 +2048,15 @@ Version 0.0.1.2 newKeys = _.keys(props); _.each(newKeys, function(columnName) { + c = this.column(columnName); + // check if we're trying to update a computed column. If so + // fail. + if (c.isComputed()) { + throw "You're trying to update a computed column. Those get computed!"; + } + // test if the value passes the type test var Type = Miso.types[c.type]; @@ -1971,11 +2073,28 @@ Version 0.0.1.2 } else { throw("incorrect value '" + props[c.name] + "' of type " + Miso.typeOf(props[c.name], c) + - " passed to column with type " + c.type); + " passed to column '" + c.name + "' with type " + c.type); } } c.data[rowIndex] = props[c.name]; }, this); + + // do we have any computed columns? if so we need to update + // the row. + if (typeof this._computedColumns !== "undefined") { + _.each(this._computedColumns, function(column) { + + // compute the complete row: + var newrow = _.extend({}, row, props); + + var oldValue = newrow[column.name]; + var newValue = column.compute(newrow, rowIndex); + // if this is actually a new value, then add it to the delta. + if (oldValue !== newValue) { + props[column.name] = newValue; + } + }); + } deltas.push( { _id : row._id, old : row, changed : props } ); }, this); @@ -2464,7 +2583,7 @@ Version 0.0.1.2 type : "GET", async : true, xhr : function() { - return new global.XMLHttpRequest(); + return global.ActiveXObject ? new global.ActiveXObject("Microsoft.XMLHTTP") : new global.XMLHttpRequest(); } }, rparams = /\?/; diff --git a/dist/miso.ds.deps.0.2.1.js b/dist/miso.ds.deps.0.2.2.js similarity index 73% rename from dist/miso.ds.deps.0.2.1.js rename to dist/miso.ds.deps.0.2.2.js index 84b87d5..d304add 100644 --- a/dist/miso.ds.deps.0.2.1.js +++ b/dist/miso.ds.deps.0.2.2.js @@ -1,5 +1,5 @@ /** -* Miso.Dataset - v0.2.1 - 6/29/2012 +* Miso.Dataset - v0.2.2 - 9/3/2012 * http://github.com/misoproject/dataset * Copyright (c) 2012 Alex Graul, Irene Ros; * Dual Licensed: MIT, GPL @@ -8,31 +8,40 @@ */ // moment.js -// version : 1.6.2 +// version : 1.7.0 // author : Tim Wood // license : MIT // momentjs.com (function (Date, undefined) { + /************************************ + Constants + ************************************/ + var moment, - VERSION = "1.6.2", + VERSION = "1.7.0", round = Math.round, i, // internal storage for language config files languages = {}, currentLanguage = 'en', // check for nodeJS - hasModule = (typeof module !== 'undefined'), + hasModule = (typeof module !== 'undefined' && module.exports), - // parameters to check for on the lang config - langConfigProperties = 'months|monthsShort|monthsParse|weekdays|weekdaysShort|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'), + // Parameters to check for on the lang config. This list of properties + // will be inherited from English if not provided in a language + // definition. monthsParse is also a lang config property, but it + // cannot be inherited and as such cannot be enumerated here. + langConfigProperties = 'months|monthsShort|weekdays|weekdaysShort|weekdaysMin|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'), // ASP.NET json date format regex aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, // format tokens - formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|dddd?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?|LT|LL?L?L?)/g, + formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?)/g, + localFormattingTokens = /(LT|LL?L?L?)/g, + formattingRemoveEscapes = /(^\[)|(\\)|\]$/g, // parsing tokens parseMultipleFormatChunker = /([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi, @@ -41,7 +50,7 @@ parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 parseTokenThreeDigits = /\d{3}/, // 000 - 999 - parseTokenFourDigits = /\d{4}/, // 0000 - 9999 + parseTokenFourDigits = /\d{1,4}/, // 0 - 9999 parseTokenWord = /[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i, // any word characters or numbers parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z parseTokenT = /T/i, // T (ISO seperator) @@ -72,20 +81,77 @@ 'Days' : 864e5, 'Months' : 2592e6, 'Years' : 31536e6 - }; + }, + + // format function strings + formatFunctions = {}, + + /* + * moment.fn.format uses new Function() to create an inlined formatting function. + * Results are a 3x speed boost + * http://jsperf.com/momentjs-cached-format-functions + * + * These strings are appended into a function using replaceFormatTokens and makeFormatFunction + */ + formatFunctionStrings = { + // a = placeholder + // b = placeholder + // t = the current moment being formatted + // v = getValueAtKey function + // o = language.ordinal function + // p = leftZeroFill function + // m = language.meridiem value or function + M : '(a=t.month()+1)', + MMM : 'v("monthsShort",t.month())', + MMMM : 'v("months",t.month())', + D : '(a=t.date())', + DDD : '(a=new Date(t.year(),t.month(),t.date()),b=new Date(t.year(),0,1),a=~~(((a-b)/864e5)+1.5))', + d : '(a=t.day())', + dd : 'v("weekdaysMin",t.day())', + ddd : 'v("weekdaysShort",t.day())', + dddd : 'v("weekdays",t.day())', + w : '(a=new Date(t.year(),t.month(),t.date()-t.day()+5),b=new Date(a.getFullYear(),0,4),a=~~((a-b)/864e5/7+1.5))', + YY : 'p(t.year()%100,2)', + YYYY : 'p(t.year(),4)', + a : 'm(t.hours(),t.minutes(),!0)', + A : 'm(t.hours(),t.minutes(),!1)', + H : 't.hours()', + h : 't.hours()%12||12', + m : 't.minutes()', + s : 't.seconds()', + S : '~~(t.milliseconds()/100)', + SS : 'p(~~(t.milliseconds()/10),2)', + SSS : 'p(t.milliseconds(),3)', + Z : '((a=-t.zone())<0?((a=-a),"-"):"+")+p(~~(a/60),2)+":"+p(~~a%60,2)', + ZZ : '((a=-t.zone())<0?((a=-a),"-"):"+")+p(~~(10*a/6),4)' + }, + + ordinalizeTokens = 'DDD w M D d'.split(' '), + paddedTokens = 'M D H h m s w'.split(' '); + + while (ordinalizeTokens.length) { + i = ordinalizeTokens.pop(); + formatFunctionStrings[i + 'o'] = formatFunctionStrings[i] + '+o(a)'; + } + while (paddedTokens.length) { + i = paddedTokens.pop(); + formatFunctionStrings[i + i] = 'p(' + formatFunctionStrings[i] + ',2)'; + } + formatFunctionStrings.DDDD = 'p(' + formatFunctionStrings.DDD + ',3)'; + + + /************************************ + Constructors + ************************************/ + // Moment prototype object - function Moment(date, isUTC) { + function Moment(date, isUTC, lang) { this._d = date; this._isUTC = !!isUTC; - } - - function absRound(number) { - if (number < 0) { - return Math.ceil(number); - } else { - return Math.floor(number); - } + this._a = date._a || null; + date._a = null; + this._lang = lang || false; } // Duration Constructor @@ -138,6 +204,22 @@ years += absRound(months / 12); data.years = years; + + this._lang = false; + } + + + /************************************ + Helpers + ************************************/ + + + function absRound(number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } } // left zero fill a number @@ -176,141 +258,152 @@ return Object.prototype.toString.call(input) === '[object Array]'; } + // compare two arrays, return the number of differences + function compareArrays(array1, array2) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if (~~array1[i] !== ~~array2[i]) { + diffs++; + } + } + return diffs + lengthDiff; + } + // convert an array to a date. // the array should mirror the parameters below // note: all values past the year are optional and will default to the lowest possible value. // [year, month, day , hour, minute, second, millisecond] - function dateFromArray(input) { - return new Date(input[0], input[1] || 0, input[2] || 1, input[3] || 0, input[4] || 0, input[5] || 0, input[6] || 0); + function dateFromArray(input, asUTC) { + var i, date; + for (i = 1; i < 7; i++) { + input[i] = (input[i] == null) ? (i === 2 ? 1 : 0) : input[i]; + } + // we store whether we used utc or not in the input array + input[7] = asUTC; + date = new Date(0); + if (asUTC) { + date.setUTCFullYear(input[0], input[1], input[2]); + date.setUTCHours(input[3], input[4], input[5], input[6]); + } else { + date.setFullYear(input[0], input[1], input[2]); + date.setHours(input[3], input[4], input[5], input[6]); + } + date._a = input; + return date; + } + + // Loads a language definition into the `languages` cache. The function + // takes a key and optionally values. If not in the browser and no values + // are provided, it will load the language file module. As a convenience, + // this function also returns the language values. + function loadLang(key, values) { + var i, m, + parse = []; + + if (!values && hasModule) { + values = require('./lang/' + key); + } + + for (i = 0; i < langConfigProperties.length; i++) { + // If a language definition does not provide a value, inherit + // from English + values[langConfigProperties[i]] = values[langConfigProperties[i]] || + languages.en[langConfigProperties[i]]; + } + + for (i = 0; i < 12; i++) { + m = moment([2000, i]); + parse[i] = new RegExp('^' + (values.months[i] || values.months(m, '')) + + '|^' + (values.monthsShort[i] || values.monthsShort(m, '')).replace('.', ''), 'i'); + } + values.monthsParse = values.monthsParse || parse; + + languages[key] = values; + + return values; + } + + // Determines which language definition to use and returns it. + // + // With no parameters, it will return the global language. If you + // pass in a language key, such as 'en', it will return the + // definition for 'en', so long as 'en' has already been loaded using + // moment.lang. If you pass in a moment or duration instance, it + // will decide the language based on that, or default to the global + // language. + function getLangDefinition(m) { + var langKey = (typeof m === 'string') && m || + m && m._lang || + null; + + return langKey ? (languages[langKey] || loadLang(langKey)) : moment; + } + + + /************************************ + Formatting + ************************************/ + + + // helper for building inline formatting functions + function replaceFormatTokens(token) { + return formatFunctionStrings[token] ? + ("'+(" + formatFunctionStrings[token] + ")+'") : + token.replace(formattingRemoveEscapes, "").replace(/\\?'/g, "\\'"); + } + + // helper for recursing long date formatting tokens + function replaceLongDateFormatTokens(input) { + return getLangDefinition().longDateFormat[input] || input; + } + + function makeFormatFunction(format) { + var output = "var a,b;return '" + + format.replace(formattingTokens, replaceFormatTokens) + "';", + Fn = Function; // get around jshint + // t = the current moment being formatted + // v = getValueAtKey function + // o = language.ordinal function + // p = leftZeroFill function + // m = language.meridiem value or function + return new Fn('t', 'v', 'o', 'p', 'm', output); + } + + function makeOrGetFormatFunction(format) { + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } + return formatFunctions[format]; } // format date using native date object - function formatMoment(m, inputString) { - var currentMonth = m.month(), - currentDate = m.date(), - currentYear = m.year(), - currentDay = m.day(), - currentHours = m.hours(), - currentMinutes = m.minutes(), - currentSeconds = m.seconds(), - currentMilliseconds = m.milliseconds(), - currentZone = -m.zone(), - ordinal = moment.ordinal, - meridiem = moment.meridiem; - // check if the character is a format - // return formatted string or non string. - // - // uses switch/case instead of an object of named functions (like http://phpjs.org/functions/date:380) - // for minification and performance - // see http://jsperf.com/object-of-functions-vs-switch for performance comparison - function replaceFunction(input) { - // create a couple variables to be used later inside one of the cases. - var a, b; - switch (input) { - // MONTH - case 'M' : - return currentMonth + 1; - case 'Mo' : - return (currentMonth + 1) + ordinal(currentMonth + 1); - case 'MM' : - return leftZeroFill(currentMonth + 1, 2); - case 'MMM' : - return moment.monthsShort[currentMonth]; - case 'MMMM' : - return moment.months[currentMonth]; - // DAY OF MONTH - case 'D' : - return currentDate; - case 'Do' : - return currentDate + ordinal(currentDate); - case 'DD' : - return leftZeroFill(currentDate, 2); - // DAY OF YEAR - case 'DDD' : - a = new Date(currentYear, currentMonth, currentDate); - b = new Date(currentYear, 0, 1); - return ~~ (((a - b) / 864e5) + 1.5); - case 'DDDo' : - a = replaceFunction('DDD'); - return a + ordinal(a); - case 'DDDD' : - return leftZeroFill(replaceFunction('DDD'), 3); - // WEEKDAY - case 'd' : - return currentDay; - case 'do' : - return currentDay + ordinal(currentDay); - case 'ddd' : - return moment.weekdaysShort[currentDay]; - case 'dddd' : - return moment.weekdays[currentDay]; - // WEEK OF YEAR - case 'w' : - a = new Date(currentYear, currentMonth, currentDate - currentDay + 5); - b = new Date(a.getFullYear(), 0, 4); - return ~~ ((a - b) / 864e5 / 7 + 1.5); - case 'wo' : - a = replaceFunction('w'); - return a + ordinal(a); - case 'ww' : - return leftZeroFill(replaceFunction('w'), 2); - // YEAR - case 'YY' : - return leftZeroFill(currentYear % 100, 2); - case 'YYYY' : - return currentYear; - // AM / PM - case 'a' : - return meridiem ? meridiem(currentHours, currentMinutes, false) : (currentHours > 11 ? 'pm' : 'am'); - case 'A' : - return meridiem ? meridiem(currentHours, currentMinutes, true) : (currentHours > 11 ? 'PM' : 'AM'); - // 24 HOUR - case 'H' : - return currentHours; - case 'HH' : - return leftZeroFill(currentHours, 2); - // 12 HOUR - case 'h' : - return currentHours % 12 || 12; - case 'hh' : - return leftZeroFill(currentHours % 12 || 12, 2); - // MINUTE - case 'm' : - return currentMinutes; - case 'mm' : - return leftZeroFill(currentMinutes, 2); - // SECOND - case 's' : - return currentSeconds; - case 'ss' : - return leftZeroFill(currentSeconds, 2); - // MILLISECONDS - case 'S' : - return ~~ (currentMilliseconds / 100); - case 'SS' : - return leftZeroFill(~~(currentMilliseconds / 10), 2); - case 'SSS' : - return leftZeroFill(currentMilliseconds, 3); - // TIMEZONE - case 'Z' : - return (currentZone < 0 ? '-' : '+') + leftZeroFill(~~(Math.abs(currentZone) / 60), 2) + ':' + leftZeroFill(~~(Math.abs(currentZone) % 60), 2); - case 'ZZ' : - return (currentZone < 0 ? '-' : '+') + leftZeroFill(~~(10 * Math.abs(currentZone) / 6), 4); - // LONG DATES - case 'L' : - case 'LL' : - case 'LLL' : - case 'LLLL' : - case 'LT' : - return formatMoment(m, moment.longDateFormat[input]); - // DEFAULT - default : - return input.replace(/(^\[)|(\\)|\]$/g, ""); - } + function formatMoment(m, format) { + var lang = getLangDefinition(m); + + function getValueFromArray(key, index) { + return lang[key].call ? lang[key](m, format) : lang[key][index]; + } + + while (localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + } + + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); } - return inputString.replace(formattingTokens, replaceFunction); + + return formatFunctions[format](m, getValueFromArray, lang.ordinal, leftZeroFill, lang.meridiem); } + + /************************************ + Parsing + ************************************/ + + // get the regex to find the next token function getParseRegexForToken(token) { switch (token) { @@ -325,6 +418,7 @@ return parseTokenOneToThreeDigits; case 'MMM': case 'MMMM': + case 'dd': case 'ddd': case 'dddd': case 'a': @@ -337,7 +431,6 @@ return parseTokenT; case 'MM': case 'DD': - case 'dd': case 'YY': case 'HH': case 'hh': @@ -369,7 +462,7 @@ case 'MMM' : // fall through to MMMM case 'MMMM' : for (a = 0; a < 12; a++) { - if (moment.monthsParse[a].test(input)) { + if (getLangDefinition().monthsParse[a].test(input)) { datePartArray[1] = a; break; } @@ -380,7 +473,9 @@ case 'DD' : // fall through to DDDD case 'DDD' : // fall through to DDDD case 'DDDD' : - datePartArray[2] = ~~input; + if (input != null) { + datePartArray[2] = ~~input; + } break; // YEAR case 'YY' : @@ -465,21 +560,7 @@ datePartArray[3] += config.tzh; datePartArray[4] += config.tzm; // return - return config.isUTC ? new Date(Date.UTC.apply({}, datePartArray)) : dateFromArray(datePartArray); - } - - // compare two arrays, return the number of differences - function compareArrays(array1, array2) { - var len = Math.min(array1.length, array2.length), - lengthDiff = Math.abs(array1.length - array2.length), - diffs = 0, - i; - for (i = 0; i < len; i++) { - if (~~array1[i] !== ~~array2[i]) { - diffs++; - } - } - return diffs + lengthDiff; + return dateFromArray(datePartArray, config.isUTC); } // date from string and array of format strings @@ -521,15 +602,21 @@ return new Date(string); } + + /************************************ + Relative Time + ************************************/ + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture) { - var rt = moment.relativeTime[string]; + function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { + var rt = lang.relativeTime[string]; return (typeof rt === 'function') ? rt(number || 1, !!withoutSuffix, string, isFuture) : rt.replace(/%d/i, number || 1); } - function relativeTime(milliseconds, withoutSuffix) { + function relativeTime(milliseconds, withoutSuffix, lang) { var seconds = round(Math.abs(milliseconds) / 1000), minutes = round(seconds / 60), hours = round(minutes / 60), @@ -547,20 +634,25 @@ years === 1 && ['y'] || ['yy', years]; args[2] = withoutSuffix; args[3] = milliseconds > 0; + args[4] = lang; return substituteTimeAgo.apply({}, args); } + + /************************************ + Top Level Functions + ************************************/ + + moment = function (input, format) { if (input === null || input === '') { return null; } var date, - matched, - isUTC; + matched; // parse Moment object if (moment.isMoment(input)) { - date = new Date(+input._d); - isUTC = input._isUTC; + return new Moment(new Date(+input._d), input._isUTC, input._lang); // parse string and format } else if (format) { if (isArray(format)) { @@ -578,17 +670,23 @@ typeof input === 'string' ? makeDateFromString(input) : new Date(input); } - return new Moment(date, isUTC); + + return new Moment(date); }; // creating with utc moment.utc = function (input, format) { if (isArray(input)) { - return new Moment(new Date(Date.UTC.apply({}, input)), true); + return new Moment(dateFromArray(input, true), true); + } + // if we don't have a timezone, we need to add one to trigger parsing into utc + if (typeof input === 'string' && !parseTokenTimezone.exec(input)) { + input += ' +0000'; + if (format) { + format += ' Z'; + } } - return (format && input) ? - moment(input + ' +0000', format + ' Z').utc() : - moment(input && !parseTokenTimezone.exec(input) ? input + '+0000' : input).utc(); + return moment(input, format).utc(); }; // creating with unix timestamp (in seconds) @@ -600,7 +698,8 @@ moment.duration = function (input, key) { var isDuration = moment.isDuration(input), isNumber = (typeof input === 'number'), - duration = (isDuration ? input._data : (isNumber ? {} : input)); + duration = (isDuration ? input._data : (isNumber ? {} : input)), + ret; if (isNumber) { if (key) { @@ -610,7 +709,13 @@ } } - return new Duration(duration); + ret = new Duration(duration); + + if (isDuration) { + ret._lang = input._lang; + } + + return ret; }; // humanizeDuration @@ -626,40 +731,49 @@ // default format moment.defaultFormat = isoFormat; - // language switching and caching + // This function will load languages and then set the global language. If + // no arguments are passed in, it will simply return the current global + // language key. moment.lang = function (key, values) { - var i, req, - parse = []; + var i; + if (!key) { return currentLanguage; } - if (values) { - for (i = 0; i < 12; i++) { - parse[i] = new RegExp('^' + values.months[i] + '|^' + values.monthsShort[i].replace('.', ''), 'i'); - } - values.monthsParse = values.monthsParse || parse; - languages[key] = values; + if (values || !languages[key]) { + loadLang(key, values); } if (languages[key]) { + // deprecated, to get the language definition variables, use the + // moment.fn.lang method or the getLangDefinition function. for (i = 0; i < langConfigProperties.length; i++) { - moment[langConfigProperties[i]] = languages[key][langConfigProperties[i]] || - languages.en[langConfigProperties[i]]; + moment[langConfigProperties[i]] = languages[key][langConfigProperties[i]]; } + moment.monthsParse = languages[key].monthsParse; currentLanguage = key; - } else { - if (hasModule) { - req = require('./lang/' + key); - moment.lang(key, req); - } } }; - // set default language + // returns language data + moment.langData = getLangDefinition; + + // compare moment object + moment.isMoment = function (obj) { + return obj instanceof Moment; + }; + + // for typechecking Duration objects + moment.isDuration = function (obj) { + return obj instanceof Duration; + }; + + // Set default language, other languages will inherit from English. moment.lang('en', { months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), longDateFormat : { LT : "h:mm A", L : "MM/DD/YYYY", @@ -667,7 +781,13 @@ LLL : "MMMM D YYYY LT", LLLL : "dddd, MMMM D YYYY LT" }, - meridiem : false, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + }, calendar : { sameDay : '[Today at] LT', nextDay : '[Tomorrow at] LT', @@ -700,17 +820,12 @@ } }); - // compare moment object - moment.isMoment = function (obj) { - return obj instanceof Moment; - }; - // for typechecking Duration objects - moment.isDuration = function (obj) { - return obj instanceof Duration; - }; + /************************************ + Moment Prototype + ************************************/ + - // shortcut for prototype moment.fn = Moment.prototype = { clone : function () { @@ -733,6 +848,27 @@ return this._d; }, + toArray : function () { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hours(), + m.minutes(), + m.seconds(), + m.milliseconds(), + !!this._isUTC + ]; + }, + + isValid : function () { + if (this._a) { + return !compareArrays(this._a, (this._a[7] ? moment.utc(this) : this).toArray()); + } + return !isNaN(this._d.getTime()); + }, + utc : function () { this._isUTC = true; return this; @@ -783,7 +919,7 @@ }, from : function (time, withoutSuffix) { - return moment.duration(this.diff(time)).humanize(!withoutSuffix); + return moment.duration(this.diff(time)).lang(this._lang).humanize(!withoutSuffix); }, fromNow : function (withoutSuffix) { @@ -792,7 +928,7 @@ calendar : function () { var diff = this.diff(moment().sod(), 'days', true), - calendar = moment.calendar, + calendar = this.lang().calendar, allElse = calendar.sameElse, format = diff < -6 ? allElse : diff < -1 ? calendar.lastWeek : @@ -819,20 +955,43 @@ this.add({ d : input - day }); }, + startOf: function (val) { + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (val.replace(/s$/, '')) { + case 'year': + this.month(0); + /* falls through */ + case 'month': + this.date(1); + /* falls through */ + case 'day': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + /* falls through */ + } + return this; + }, + + endOf: function (val) { + return this.startOf(val).add(val.replace(/s?$/, 's'), 1).subtract('ms', 1); + }, + sod: function () { - return moment(this) - .hours(0) - .minutes(0) - .seconds(0) - .milliseconds(0); + return this.clone().startOf('day'); }, eod: function () { // end of day = start of day plus 1 day, minus 1 millisecond - return this.sod().add({ - d : 1, - ms : -1 - }); + return this.clone().endOf('day'); }, zone : function () { @@ -840,7 +999,19 @@ }, daysInMonth : function () { - return moment(this).month(this.month() + 1).date(0).date(); + return moment.utc([this.year(), this.month() + 1, 0]).date(); + }, + + // If passed a language key, it will set the language for this + // instance. Otherwise, it will return the language configuration + // variables for this instance. + lang : function (lang) { + if (lang === undefined) { + return getLangDefinition(this); + } else { + this._lang = lang; + return this; + } } }; @@ -865,6 +1036,12 @@ // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') makeGetterAndSetter('year', 'FullYear'); + + /************************************ + Duration Prototype + ************************************/ + + moment.duration.fn = Duration.prototype = { weeks : function () { return absRound(this.days() / 7); @@ -878,15 +1055,17 @@ humanize : function (withSuffix) { var difference = +this, - rel = moment.relativeTime, - output = relativeTime(difference, !withSuffix); + rel = this.lang().relativeTime, + output = relativeTime(difference, !withSuffix, this.lang()); if (withSuffix) { output = (difference <= 0 ? rel.past : rel.future).replace(/%s/i, output); } return output; - } + }, + + lang : moment.fn.lang }; function makeDurationGetter(name) { @@ -910,13 +1089,22 @@ makeDurationAsGetter('Weeks', 6048e5); + + /************************************ + Exposing Moment + ************************************/ + + // CommonJS module is defined if (hasModule) { module.exports = moment; } /*global ender:false */ - if (typeof window !== 'undefined' && typeof ender === 'undefined') { - window.moment = moment; + if (typeof ender === 'undefined') { + // here, `this` means `window` in the browser, or `global` on the server + // add `moment` as a global object via a string identifier, + // for Closure Compiler "advanced" mode + this['moment'] = moment; } /*global define:false */ if (typeof define === "function" && define.amd) { @@ -924,9 +1112,9 @@ return moment; }); } -})(Date); +}).call(this, Date); /*! - * Lo-Dash v0.3.2 + * Lo-Dash v0.6.1 * Copyright 2012 John-David Dalton * Based on Underscore.js 1.3.3, copyright 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. * @@ -935,33 +1123,80 @@ ;(function(window, undefined) { 'use strict'; + /** + * Used to cache the last `_.templateSettings.evaluate` delimiter to avoid + * unnecessarily assigning `reEvaluateDelimiter` a new generated regexp. + * Assigned in `_.template`. + */ + var lastEvaluateDelimiter; + + /** + * Used to cache the last template `options.variable` to avoid unnecessarily + * assigning `reDoubleVariable` a new generated regexp. Assigned in `_.template`. + */ + var lastVariable; + + /** + * Used to match potentially incorrect data object references, like `obj.obj`, + * in compiled templates. Assigned in `_.template`. + */ + var reDoubleVariable; + + /** + * Used to match "evaluate" delimiters, including internal delimiters, + * in template text. Assigned in `_.template`. + */ + var reEvaluateDelimiter; + /** Detect free variable `exports` */ var freeExports = typeof exports == 'object' && exports && (typeof global == 'object' && global && global == global.global && (window = global), exports); - /** - * Detect the JScript [[DontEnum]] bug: - * In IE < 9 an objects own properties, shadowing non-enumerable ones, are - * made non-enumerable as well. - */ - var hasDontEnumBug = !{ 'valueOf': 0 }.propertyIsEnumerable('valueOf'); + /** Native prototype shortcuts */ + var ArrayProto = Array.prototype, + BoolProto = Boolean.prototype, + ObjectProto = Object.prototype, + NumberProto = Number.prototype, + StringProto = String.prototype; /** Used to generate unique IDs */ var idCounter = 0; + /** Used by `cachedContains` as the default size when optimizations are enabled for large arrays */ + var largeArraySize = 30; + /** Used to restore the original `_` reference in `noConflict` */ var oldDash = window._; + /** Used to detect delimiter values that should be processed by `tokenizeEvaluate` */ + var reComplexDelimiter = /[-+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/; + + /** Used to match HTML entities */ + var reEscapedHtml = /&(?:amp|lt|gt|quot|#x27);/g; + + /** 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 regexp flags from their coerced string values */ + var reFlags = /\w*$/; + + /** Used to insert the data object variable into compiled template source */ + var reInsertVariable = /(?:__e|__t = )\(\s*(?![\d\s"']|this\.)/g; + /** Used to detect if a method is native */ - var reNative = RegExp('^' + ({}.valueOf + '') - .replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&') - .replace(/valueOf|for [^\]]+/g, '.+?') + '$'); + var reNative = RegExp('^' + + (ObjectProto.valueOf + '') + .replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&') + .replace(/valueOf|for [^\]]+/g, '.+?') + '$' + ); - /** Used to match tokens in template text */ + /** Used to match internally used tokens in template text */ var reToken = /__token__(\d+)/g; - /** Used to match unescaped characters in HTML */ - var reUnescapedHtml = /[&<"']/g; + /** 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; @@ -981,25 +1216,162 @@ /** Used to store tokenized template text snippets */ var tokenized = []; - /** Detect if sourceURL syntax is usable without erroring */ + /** Native method shortcuts */ + var concat = ArrayProto.concat, + hasOwnProperty = ObjectProto.hasOwnProperty, + push = ArrayProto.push, + propertyIsEnumerable = ObjectProto.propertyIsEnumerable, + slice = ArrayProto.slice, + toString = ObjectProto.toString; + + /* Native method shortcuts for methods with the same name as other `lodash` methods */ + var nativeBind = reNative.test(nativeBind = slice.bind) && nativeBind, + nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, + nativeIsFinite = window.isFinite, + nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys; + + /** `Object#toString` result shortcuts */ + var argsClass = '[object Arguments]', + arrayClass = '[object Array]', + boolClass = '[object Boolean]', + dateClass = '[object Date]', + funcClass = '[object Function]', + numberClass = '[object Number]', + objectClass = '[object Object]', + regexpClass = '[object RegExp]', + stringClass = '[object String]'; + + /** Timer shortcuts */ + var clearTimeout = window.clearTimeout, + setTimeout = window.setTimeout; + + /** + * Detect the JScript [[DontEnum]] bug: + * + * In IE < 9 an objects own properties, shadowing non-enumerable ones, are + * made non-enumerable as well. + */ + var hasDontEnumBug; + + /** + * Detect if `Array#shift` and `Array#splice` augment array-like objects + * incorrectly: + * + * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()` + * and `splice()` functions that fail to remove the last element, `value[0]`, + * of array-like objects even though the `length` property is set to `0`. + * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` + * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. + */ + var hasObjectSpliceBug; + + /** Detect if own properties are iterated after inherited properties (IE < 9) */ + var iteratesOwnLast; + + /** Detect if an `arguments` object's indexes are non-enumerable (IE < 9) */ + var noArgsEnum = true; + + (function() { + var object = { '0': 1, 'length': 1 }, + props = []; + + function ctor() { this.x = 1; } + ctor.prototype = { 'valueOf': 1, 'y': 1 }; + for (var prop in new ctor) { props.push(prop); } + for (prop in arguments) { noArgsEnum = !prop; } + + hasDontEnumBug = (props + '').length < 4; + iteratesOwnLast = props[0] != 'x'; + hasObjectSpliceBug = (props.splice.call(object, 0, 1), object[0]); + }(1)); + + /** Detect if an `arguments` object's [[Class]] is unresolvable (Firefox < 4, IE < 9) */ + var noArgsClass = !isArguments(arguments); + + /** Detect if `Array#slice` cannot be used to convert strings to arrays (Opera < 10.52) */ + var noArraySliceOnStrings = slice.call('x')[0] != 'x'; + + /** + * Detect lack of support for accessing string characters by index: + * + * IE < 8 can't access characters by index and IE 8 can only access + * characters by index on string literals. + */ + var noCharByIndex = ('x'[0] + Object('x')[0]) != 'xx'; + + /** + * Detect if a node's [[Class]] is unresolvable (IE < 9) + * and that the JS engine won't error when attempting to coerce an object to + * a string without a `toString` property value of `typeof` "function". + */ + try { + var noNodeClass = ({ 'toString': 0 } + '', toString.call(window.document || 0) == objectClass); + } catch(e) { } + + /* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */ + var isBindFast = nativeBind && /\n|Opera/.test(nativeBind + toString.call(window.opera)); + + /* Detect if `Object.keys` exists and is inferred to be fast (IE, Opera, V8) */ + var isKeysFast = nativeKeys && /^.+$|true/.test(nativeKeys + !!window.attachEvent); + + /* Detect if strict mode, "use strict", is inferred to be fast (V8) */ + var isStrictFast = !isBindFast; + + /** + * Detect if sourceURL syntax is usable without erroring: + * + * The JS engine in Adobe products, like InDesign, will throw a syntax error + * when it encounters a single line comment beginning with the `@` symbol. + * + * The JS engine in Narwhal will generate the function `function anonymous(){//}` + * and throw a syntax error. + * + * Avoid comments beginning `@` symbols in IE because they are part of its + * non-standard conditional compilation support. + * http://msdn.microsoft.com/en-us/library/121hztk3(v=vs.94).aspx + */ try { - // Adobe's and Narwhal's JS engines will error - var useSourceURL = (Function('//@')(), true); + var useSourceURL = (Function('//@')(), !window.attachEvent); } catch(e){ } + /** Used to identify object classifications that are array-like */ + var arrayLikeClasses = {}; + arrayLikeClasses[boolClass] = arrayLikeClasses[dateClass] = arrayLikeClasses[funcClass] = + arrayLikeClasses[numberClass] = arrayLikeClasses[objectClass] = arrayLikeClasses[regexpClass] = false; + arrayLikeClasses[argsClass] = arrayLikeClasses[arrayClass] = arrayLikeClasses[stringClass] = true; + + /** Used to identify object classifications that `_.clone` supports */ + var cloneableClasses = {}; + cloneableClasses[argsClass] = cloneableClasses[funcClass] = false; + cloneableClasses[arrayClass] = cloneableClasses[boolClass] = cloneableClasses[dateClass] = + cloneableClasses[numberClass] = cloneableClasses[objectClass] = cloneableClasses[regexpClass] = + cloneableClasses[stringClass] = true; + /** - * Used to escape characters for inclusion in HTML. - * The `>` and `/` characters 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 (semi-related fun fact) + * 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 = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': "'" + }; + /** Used to determine if values are of the language type Object */ var objectTypes = { 'boolean': false, @@ -1007,7 +1379,8 @@ 'object': true, 'number': false, 'string': false, - 'undefined': false + 'undefined': false, + 'unknown': true }; /** Used to escape characters for inclusion in compiled string literals */ @@ -1021,39 +1394,6 @@ '\u2029': 'u2029' }; - /** Object#toString result shortcuts */ - var arrayClass = '[object Array]', - boolClass = '[object Boolean]', - dateClass = '[object Date]', - funcClass = '[object Function]', - numberClass = '[object Number]', - regexpClass = '[object RegExp]', - stringClass = '[object String]'; - - /** Native prototype shortcuts */ - var ArrayProto = Array.prototype, - ObjectProto = Object.prototype; - - /** Native method shortcuts */ - var concat = ArrayProto.concat, - hasOwnProperty = ObjectProto.hasOwnProperty, - push = ArrayProto.push, - slice = ArrayProto.slice, - toString = ObjectProto.toString; - - /* Used if `Function#bind` exists and is inferred to be fast (i.e. all but V8) */ - var nativeBind = reNative.test(nativeBind = slice.bind) && - /\n|Opera/.test(nativeBind + toString.call(window.opera)) && nativeBind; - - /* Native method shortcuts for methods with the same name as other `lodash` methods */ - var nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, - nativeIsFinite = window.isFinite, - nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys; - - /** Timer shortcuts */ - var clearTimeout = window.clearTimeout, - setTimeout = window.setTimeout; - /*--------------------------------------------------------------------------*/ /** @@ -1085,8 +1425,9 @@ } /** - * By default, Lo-Dash uses ERB-style template delimiters, change the - * following template settings to use alternative delimiters. + * 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 _ @@ -1128,7 +1469,7 @@ * @memberOf _.templateSettings * @type String */ - 'variable': 'obj' + 'variable': '' }; /*--------------------------------------------------------------------------*/ @@ -1141,8 +1482,13 @@ * @returns {String} Returns the interpolated text. */ var iteratorTemplate = template( + // conditional strict mode + '<% if (useStrict) { %>\'use strict\';\n<% } %>' + + + // the `iteratee` may be reassigned by the `top` snippet + 'var index, value, iteratee = <%= firstArg %>, ' + // assign the `result` variable an initial value - 'var index, result<% if (init) { %> = <%= init %><% } %>;\n' + + 'result<% if (init) { %> = <%= init %><% } %>;\n' + // add code to exit early or do so if the first argument is falsey '<%= exit %>;\n' + // add code after the exit snippet but before the iteration branches @@ -1150,29 +1496,38 @@ // the following branch is for iterating arrays and array-like objects '<% if (arrayBranch) { %>' + - 'var length = <%= firstArg %>.length; index = -1;' + - ' <% if (objectBranch) { %>\nif (length === length >>> 0) {<% } %>\n' + + 'var length = iteratee.length; index = -1;' + + ' <% if (objectBranch) { %>\nif (length > -1 && length === length >>> 0) {<% } %>' + + + // add support for accessing string characters by index if needed + ' <% if (noCharByIndex) { %>\n' + + ' if (toString.call(iteratee) == stringClass) {\n' + + ' iteratee = iteratee.split(\'\')\n' + + ' }' + + ' <% } %>\n' + + ' <%= arrayBranch.beforeLoop %>;\n' + - ' while (<%= arrayBranch.loopExp %>) {\n' + - ' <%= arrayBranch.inLoop %>;\n' + + ' while (++index < length) {\n' + + ' value = iteratee[index];\n' + + ' <%= arrayBranch.inLoop %>\n' + ' }' + - ' <% if (objectBranch) { %>\n}\n<% }' + - '}' + + ' <% if (objectBranch) { %>\n}<% } %>' + + '<% } %>' + // the following branch is for iterating an object's own/inherited properties - 'if (objectBranch) {' + - ' if (arrayBranch) { %>else {\n<% }' + - ' if (!hasDontEnumBug) { %> var skipProto = typeof <%= iteratedObject %> == \'function\';\n<% } %>' + - ' <%= objectBranch.beforeLoop %>;\n' + - ' for (<%= objectBranch.loopExp %>) {' + - ' \n<%' + - ' if (hasDontEnumBug) {' + - ' if (useHas) { %> if (<%= hasExp %>) {\n <% } %>' + - ' <%= objectBranch.inLoop %>;<%' + - ' if (useHas) { %>\n }<% }' + - ' }' + - ' else {' + - ' %>' + + '<% if (objectBranch) { %>' + + ' <% if (arrayBranch) { %>\nelse {' + + + // add support for iterating over `arguments` objects if needed + ' <% } else if (noArgsEnum) { %>\n' + + ' var length = iteratee.length; index = -1;\n' + + ' if (length && isArguments(iteratee)) {\n' + + ' while (++index < length) {\n' + + ' value = iteratee[index += \'\'];\n' + + ' <%= objectBranch.inLoop %>\n' + + ' }\n' + + ' } else {' + + ' <% } %>' + // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 // (if the prototype or a property on the prototype has been set) @@ -1180,30 +1535,60 @@ // value to `true`. Because of this Lo-Dash standardizes on skipping // the the `prototype` property of functions regardless of its // [[Enumerable]] value. - ' if (!(skipProto && index == \'prototype\')<% if (useHas) { %> && <%= hasExp %><% } %>) {\n' + - ' <%= objectBranch.inLoop %>;\n' + - ' }' + - ' <% } %>\n' + + ' <% if (!hasDontEnumBug) { %>\n' + + ' var skipProto = typeof iteratee == \'function\' && \n' + + ' propertyIsEnumerable.call(iteratee, \'prototype\');\n' + + ' <% } %>' + + + // iterate own properties using `Object.keys` if it's fast + ' <% if (isKeysFast && useHas) { %>\n' + + ' var ownIndex = -1,\n' + + ' ownProps = objectTypes[typeof iteratee] ? nativeKeys(iteratee) : [],\n' + + ' length = ownProps.length;\n\n' + + ' <%= objectBranch.beforeLoop %>;\n' + + ' while (++ownIndex < length) {\n' + + ' index = ownProps[ownIndex];\n' + + ' <% if (!hasDontEnumBug) { %>if (!(skipProto && index == \'prototype\')) {\n <% } %>' + + ' value = iteratee[index];\n' + + ' <%= objectBranch.inLoop %>\n' + + ' <% if (!hasDontEnumBug) { %>}\n<% } %>' + + ' }' + + + // else using a for-in loop + ' <% } else { %>\n' + + ' <%= objectBranch.beforeLoop %>;\n' + + ' for (index in iteratee) {' + + ' <% if (!hasDontEnumBug || useHas) { %>\n if (<%' + + ' if (!hasDontEnumBug) { %>!(skipProto && index == \'prototype\')<% }' + + ' if (!hasDontEnumBug && useHas) { %> && <% }' + + ' if (useHas) { %>hasOwnProperty.call(iteratee, index)<% }' + + ' %>) {' + + ' <% } %>\n' + + ' value = iteratee[index];\n' + + ' <%= objectBranch.inLoop %>;\n' + + ' <% if (!hasDontEnumBug || useHas) { %>}\n<% } %>' + ' }' + + ' <% } %>' + // Because IE < 9 can't set the `[[Enumerable]]` attribute of an // existing property and the `constructor` property of a prototype // defaults to non-enumerable, Lo-Dash skips the `constructor` // property when it infers it's iterating over a `prototype` object. - ' <% if (hasDontEnumBug) { %>\n' + - ' var ctor = <%= iteratedObject %>.constructor;\n' + - ' <% for (var k = 0; k < 7; k++) { %>\n' + + ' <% if (hasDontEnumBug) { %>\n\n' + + ' var ctor = iteratee.constructor;\n' + + ' <% for (var k = 0; k < 7; k++) { %>\n' + ' index = \'<%= shadowed[k] %>\';\n' + ' if (<%' + ' if (shadowed[k] == \'constructor\') {' + - ' %>!(ctor && ctor.prototype === <%= iteratedObject %>) && <%' + - ' } %><%= hasExp %>) {\n' + - ' <%= objectBranch.inLoop %>;\n' + - ' }<%' + - ' }' + - ' }' + - ' if (arrayBranch) { %>\n}<% }' + - '} %>\n' + + ' %>!(ctor && ctor.prototype === iteratee) && <%' + + ' } %>hasOwnProperty.call(iteratee, index)) {\n' + + ' value = iteratee[index];\n' + + ' <%= objectBranch.inLoop %>\n' + + ' }' + + ' <% } %>' + + ' <% } %>' + + ' <% if (arrayBranch || noArgsEnum) { %>\n}<% } %>' + + '<% } %>\n' + // add code to the bottom of the iteration function '<%= bottom %>;\n' + @@ -1213,7 +1598,8 @@ /** * Reusable iterator options shared by - * `every`, `filter`, `find`, `forEach`, `forIn`, `forOwn`, `map`, `reject`, and `some`. + * `every`, `filter`, `find`, `forEach`, `forIn`, `forOwn`, `groupBy`, `map`, + * `reject`, `some`, and `sortBy`. */ var baseIteratorOptions = { 'args': 'collection, callback, thisArg', @@ -1225,33 +1611,68 @@ 'else if (thisArg) {\n' + ' callback = iteratorBind(callback, thisArg)\n' + '}', - 'inLoop': 'callback(collection[index], index, collection)' + 'inLoop': 'if (callback(value, index, collection) === false) return result' + }; + + /** Reusable iterator options for `countBy`, `groupBy`, and `sortBy` */ + var countByIteratorOptions = { + 'init': '{}', + 'top': + 'var prop;\n' + + 'if (typeof callback != \'function\') {\n' + + ' var valueProp = callback;\n' + + ' callback = function(value) { return value[valueProp] }\n' + + '}\n' + + 'else if (thisArg) {\n' + + ' callback = iteratorBind(callback, thisArg)\n' + + '}', + 'inLoop': + 'prop = callback(value, index, collection);\n' + + '(hasOwnProperty.call(result, prop) ? result[prop]++ : result[prop] = 1)' + }; + + /** Reusable iterator options for `drop` and `pick` */ + var dropIteratorOptions = { + 'useHas': false, + 'args': 'object, callback, thisArg', + 'init': '{}', + 'top': + 'var isFunc = typeof callback == \'function\';\n' + + 'if (!isFunc) {\n' + + ' var props = concat.apply(ArrayProto, arguments)\n' + + '} else if (thisArg) {\n' + + ' callback = iteratorBind(callback, thisArg)\n' + + '}', + 'inLoop': + 'if (isFunc\n' + + ' ? !callback(value, index, object)\n' + + ' : indexOf(props, index) < 0\n' + + ') result[index] = value' }; /** Reusable iterator options for `every` and `some` */ var everyIteratorOptions = { 'init': 'true', - 'inLoop': 'if (!callback(collection[index], index, collection)) return !result' + 'inLoop': 'if (!callback(value, index, collection)) return !result' }; /** Reusable iterator options for `defaults` and `extend` */ var extendIteratorOptions = { + 'useHas': false, + 'useStrict': false, 'args': 'object', 'init': 'object', 'top': - 'for (var source, sourceIndex = 1, length = arguments.length; sourceIndex < length; sourceIndex++) {\n' + - ' source = arguments[sourceIndex];\n' + - (hasDontEnumBug ? ' if (source) {' : ''), - 'loopExp': 'index in source', - 'useHas': false, - 'inLoop': 'object[index] = source[index]', - 'bottom': (hasDontEnumBug ? ' }\n' : '') + '}' + 'for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {\n' + + ' if (iteratee = arguments[argsIndex]) {', + 'inLoop': 'result[index] = value', + 'bottom': ' }\n}' }; - /** Reusable iterator options for `filter` and `reject` */ + /** Reusable iterator options for `filter`, `reject`, and `where` */ var filterIteratorOptions = { 'init': '[]', - 'inLoop': 'callback(collection[index], index, collection) && result.push(collection[index])' + 'inLoop': 'callback(value, index, collection) && result.push(value)' }; /** Reusable iterator options for `find`, `forEach`, `forIn`, and `forOwn` */ @@ -1266,22 +1687,61 @@ } }; - /** Reusable iterator options for `invoke`, `map`, and `pluck` */ + /** Reusable iterator options for `invoke`, `map`, `pluck`, and `sortBy` */ var mapIteratorOptions = { 'init': '', 'exit': 'if (!collection) return []', 'beforeLoop': { 'array': 'result = Array(length)', - 'object': 'result = []' + 'object': 'result = ' + (isKeysFast ? 'Array(length)' : '[]') }, 'inLoop': { - 'array': 'result[index] = callback(collection[index], index, collection)', - 'object': 'result.push(callback(collection[index], index, collection))' + 'array': 'result[index] = callback(value, index, collection)', + 'object': 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '(callback(value, index, collection))' } }; /*--------------------------------------------------------------------------*/ + /** + * Creates a new function optimized for searching large arrays for a given `value`, + * starting at `fromIndex`, using strict equality for comparisons, i.e. `===`. + * + * @private + * @param {Array} array The array to search. + * @param {Mixed} value The value to search for. + * @param {Number} [fromIndex=0] The index to start searching from. + * @param {Number} [largeSize=30] The length at which an array is considered large. + * @returns {Boolean} Returns `true` if `value` is found, else `false`. + */ + function cachedContains(array, fromIndex, largeSize) { + fromIndex || (fromIndex = 0); + + var length = array.length, + isLarge = (length - fromIndex) >= (largeSize || largeArraySize), + cache = isLarge ? {} : array; + + if (isLarge) { + // init value cache + var key, + index = fromIndex - 1; + + while (++index < length) { + // manually coerce `value` to string because `hasOwnProperty`, in some + // older versions of Firefox, coerces objects incorrectly + key = array[index] + ''; + (hasOwnProperty.call(cache, key) ? cache[key] : (cache[key] = [])).push(array[index]); + } + } + return function(value) { + if (isLarge) { + var key = value + ''; + return hasOwnProperty.call(cache, key) && indexOf(cache[key], value) > -1; + } + return indexOf(cache, value, fromIndex) > -1; + } + } + /** * Creates compiled iteration functions. The iteration function will be created * to iterate over only objects if the first argument of `options.args` is @@ -1290,6 +1750,12 @@ * @private * @param {Object} [options1, options2, ...] The compile options objects. * + * useHas - A boolean to specify whether or not to use `hasOwnProperty` checks + * in the object loop. + * + * useStrict - A boolean to specify whether or not to include the ES5 + * "use strict" directive. + * * args - A string of comma separated arguments the iteration function will * accept. * @@ -1304,12 +1770,6 @@ * beforeLoop - A string or object containing an "array" or "object" property * of code to execute before the array or object loops. * - * loopExp - A string or object containing an "array" or "object" property - * of code to execute as the array or object loop expression. - * - * useHas - A boolean to specify whether or not to use `hasOwnProperty` checks - * in the object loop. - * * inLoop - A string or object containing an "array" or "object" property * of code to execute in the array or object loops. * @@ -1331,7 +1791,7 @@ 'exit': '', 'init': '', 'top': '', - 'arrayBranch': { 'beforeLoop': '', 'loopExp': '++index < length' }, + 'arrayBranch': { 'beforeLoop': '' }, 'objectBranch': { 'beforeLoop': '' } }; @@ -1340,12 +1800,12 @@ for (prop in object) { value = (value = object[prop]) == null ? '' : value; // keep this regexp explicit for the build pre-process - if (/beforeLoop|loopExp|inLoop/.test(prop)) { + if (/beforeLoop|inLoop/.test(prop)) { if (typeof value == 'string') { value = { 'array': value, 'object': value }; } - data.arrayBranch[prop] = value.array; - data.objectBranch[prop] = value.object; + data.arrayBranch[prop] = value.array || ''; + data.objectBranch[prop] = value.object || ''; } else { data[prop] = value; } @@ -1353,51 +1813,57 @@ } // set additional template `data` values var args = data.args, - arrayBranch = data.arrayBranch, - objectBranch = data.objectBranch, firstArg = /^[^,]+/.exec(args)[0], - loopExp = objectBranch.loopExp, - iteratedObject = /\S+$/.exec(loopExp || firstArg)[0]; + useStrict = data.useStrict; data.firstArg = firstArg; data.hasDontEnumBug = hasDontEnumBug; - data.hasExp = 'hasOwnProperty.call(' + iteratedObject + ', index)'; - data.iteratedObject = iteratedObject; + data.isKeysFast = isKeysFast; + data.noArgsEnum = noArgsEnum; data.shadowed = shadowed; data.useHas = data.useHas !== false; + data.useStrict = useStrict == null ? isStrictFast : useStrict; + if (data.noCharByIndex == null) { + data.noCharByIndex = noCharByIndex; + } if (!data.exit) { data.exit = 'if (!' + firstArg + ') return result'; } - if (firstArg == 'object' || !arrayBranch.inLoop) { + if (firstArg != 'collection' || !data.arrayBranch.inLoop) { data.arrayBranch = null; } - if (!loopExp) { - objectBranch.loopExp = 'index in ' + iteratedObject; - } // create the function factory var factory = Function( - 'arrayClass, funcClass, hasOwnProperty, identity, iteratorBind, objectTypes, ' + - 'slice, stringClass, toString', - '"use strict"; return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' + 'arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn, ' + + 'hasOwnProperty, identity, indexOf, isArguments, isArray, isFunction, ' + + 'isPlainObject, iteratorBind, objectClass, objectTypes, nativeKeys, ' + + 'propertyIsEnumerable, slice, stringClass, toString', + 'var callee = function(' + args + ') {\n' + iteratorTemplate(data) + '\n};\n' + + 'return callee' ); // return the compiled function return factory( - arrayClass, funcClass, hasOwnProperty, identity, iteratorBind, objectTypes, - slice, stringClass, toString + arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn, + hasOwnProperty, identity, indexOf, isArguments, isArray, isFunction, + isPlainObject, iteratorBind, objectClass, objectTypes, nativeKeys, + propertyIsEnumerable, slice, stringClass, toString ); } /** - * Used by `sortBy()` to compare values of the array returned by `toSortable()`, - * sorting them in ascending order. + * 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 `-1` if `a` < `b`, `0` if `a` == `b`, or `1` if `a` > `b`. + * @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; @@ -1407,11 +1873,13 @@ if (b === undefined) { return -1; } - return a < b ? -1 : a > b ? 1 : 0; + // ensure a stable sort in V8 and other engines + // http://code.google.com/p/v8/issues/detail?id=90 + return a < b ? -1 : a > b ? 1 : ai < bi ? -1 : 1; } /** - * Used by `template()` to replace tokens with their corresponding code snippets. + * Used by `template` to replace tokens with their corresponding code snippets. * * @private * @param {String} match The matched token. @@ -1423,7 +1891,7 @@ } /** - * Used by `template()` to escape characters for inclusion in compiled + * Used by `template` to escape characters for inclusion in compiled * string literals. * * @private @@ -1435,7 +1903,7 @@ } /** - * Used by `escape()` to escape characters for inclusion in HTML. + * Used by `escape` to convert characters to HTML entities. * * @private * @param {String} match The matched character to escape. @@ -1470,22 +1938,7 @@ } /** - * A shim implementation of `Object.keys` that produces an array of the given - * object's own enumerable property names. - * - * @private - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property names. - */ - var shimKeys = createIterator({ - 'args': 'object', - 'exit': 'if (!objectTypes[typeof object] || object === null) throw TypeError()', - 'init': '[]', - 'inLoop': 'result.push(index)' - }); - - /** - * Used by `template()` to replace "escape" template delimiters with tokens. + * Used by `template` to replace "escape" template delimiters with tokens. * * @private * @param {String} match The matched template delimiter. @@ -1493,2424 +1946,2858 @@ * @returns {String} Returns a token. */ function tokenizeEscape(match, value) { + if (match && reComplexDelimiter.test(value)) { + return ''; + } var index = tokenized.length; - tokenized[index] = "'+\n_.escape(" + value + ") +\n'"; + tokenized[index] = "' +\n__e(" + value + ") +\n'"; return token + index; } /** - * Used by `template()` to replace "interpolate" template delimiters with tokens. + * Used by `template` to replace "evaluate" template delimiters, or complex + * "escape" and "interpolate" delimiters, with tokens. * * @private * @param {String} match The matched template delimiter. - * @param {String} value The delimiter value. + * @param {String} escapeValue The complex "escape" delimiter value. + * @param {String} interpolateValue The complex "interpolate" delimiter value. + * @param {String} [evaluateValue] The "evaluate" delimiter value. * @returns {String} Returns a token. */ - function tokenizeInterpolate(match, value) { - var index = tokenized.length; - tokenized[index] = "'+\n((__t = (" + value + ")) == null ? '' : __t) +\n'"; - return token + index; + function tokenizeEvaluate(match, escapeValue, interpolateValue, evaluateValue) { + if (evaluateValue) { + var index = tokenized.length; + tokenized[index] = "';\n" + evaluateValue + ";\n__p += '"; + return token + index; + } + return escapeValue + ? tokenizeEscape(null, escapeValue) + : tokenizeInterpolate(null, interpolateValue); } /** - * Used by `template()` to replace "evaluate" template delimiters with tokens. + * Used by `template` to replace "interpolate" template delimiters with tokens. * * @private * @param {String} match The matched template delimiter. * @param {String} value The delimiter value. * @returns {String} Returns a token. */ - function tokenizeEvaluate(match, value) { + function tokenizeInterpolate(match, value) { + if (match && reComplexDelimiter.test(value)) { + return ''; + } var index = tokenized.length; - tokenized[index] = "';\n" + value + ";\n__p += '"; + tokenized[index] = "' +\n((__t = (" + value + ")) == null ? '' : __t) +\n'"; return token + index; } /** - * Converts `collection` to an array of objects by running each element through - * a transformation `callback`. Each object has a `criteria` property containing - * the transformed value to be sorted and a `value` property containing the - * original unmodified value. The `callback` is invoked with 3 arguments; - * for arrays they are (value, index, array) and for objects they are - * (value, key, object). + * Used by `unescape` to convert HTML entities to characters. * * @private - * @param {Array|Object} collection The collection to convert. - * @param {Function} callback The function called per iteration. - * @returns {Array} Returns a new array of objects to sort. + * @param {String} match The matched character to unescape. + * @returns {String} Returns the unescaped character. */ - var toSortable = createIterator(mapIteratorOptions, { - 'args': 'collection, callback', - 'inLoop': { - 'array': - 'result[index] = {\n' + - ' criteria: callback(collection[index], index, collection),\n' + - ' value: collection[index]\n' + - '}', - 'object': - 'result.push({\n' + - ' criteria: callback(collection[index], index, collection),\n' + - ' value: collection[index]\n' + - '})' - } - }); + function unescapeHtmlChar(match) { + return htmlUnescapes[match]; + } /*--------------------------------------------------------------------------*/ /** - * Checks if a given `target` value is present in a `collection` using strict - * equality for comparisons, i.e. `===`. + * Checks if `value` is an `arguments` object. * * @static * @memberOf _ - * @alias include - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Mixed} target The value to check for. - * @returns {Boolean} Returns `true` if `target` value is found, else `false`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is an `arguments` object, else `false`. * @example * - * _.contains([1, 2, 3], 3); + * (function() { return _.isArguments(arguments); })(1, 2, 3); * // => true + * + * _.isArguments([1, 2, 3]); + * // => false */ - var contains = createIterator({ - 'args': 'collection, target', - 'init': 'false', - 'inLoop': 'if (collection[index] === target) return true' - }); + function isArguments(value) { + return toString.call(value) == argsClass; + } + // fallback for browsers that can't detect `arguments` objects by [[Class]] + if (noArgsClass) { + isArguments = function(value) { + return !!(value && hasOwnProperty.call(value, 'callee')); + }; + } /** - * Checks if the `callback` returns a truthy value for **all** elements of a - * `collection`. The `callback` is bound to `thisArg` and invoked with 3 - * arguments; for arrays they are (value, index, array) and for objects they - * are (value, key, object). + * Checks if `value` is an array. * * @static * @memberOf _ - * @alias all - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Boolean} Returns `true` if all values pass the callback check, else `false`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is an array, else `false`. * @example * - * _.every([true, 1, null, 'yes'], Boolean); + * (function() { return _.isArray(arguments); })(); * // => false + * + * _.isArray([1, 2, 3]); + * // => true */ - var every = createIterator(baseIteratorOptions, everyIteratorOptions); + var isArray = nativeIsArray || function(value) { + return toString.call(value) == arrayClass; + }; /** - * Examines each value in a `collection`, returning an array of all values the - * `callback` returns truthy for. The `callback` is bound to `thisArg` and - * invoked with 3 arguments; for arrays they are (value, index, array) and for - * objects they are (value, key, object). + * Checks if `value` is a function. * * @static * @memberOf _ - * @alias select - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of values that passed callback check. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a function, else `false`. * @example * - * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); - * // => [2, 4, 6] + * _.isFunction(''.concat); + * // => true */ - var filter = createIterator(baseIteratorOptions, filterIteratorOptions); + function isFunction(value) { + return typeof value == 'function'; + } + // fallback for older versions of Chrome and Safari + if (isFunction(/x/)) { + isFunction = function(value) { + return toString.call(value) == funcClass; + }; + } /** - * Examines each value in a `collection`, returning the first one the `callback` - * returns truthy for. The function returns as soon as it finds an acceptable - * value, and does not iterate over the entire `collection`. The `callback` is - * bound to `thisArg` and invoked with 3 arguments; for arrays they are - * (value, index, array) and for objects they are (value, key, object). - * - * @static - * @memberOf _ - * @alias detect - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the value that passed the callback check, else `undefined`. - * @example + * 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. * - * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); - * // => 2 + * @private + * @param {Mixed} value The value to check. + * @param {Boolean} [skipArgsCheck=false] Internally used to skip checks for + * `arguments` objects. + * @returns {Boolean} Returns `true` if the `value` is a plain `Object` object, + * else `false`. */ - var find = createIterator(baseIteratorOptions, forEachIteratorOptions, { - 'init': '', - 'inLoop': 'if (callback(collection[index], index, collection)) return collection[index]' - }); + function isPlainObject(value, skipArgsCheck) { + return value + ? value == ObjectProto || (value.__proto__ == ObjectProto && (skipArgsCheck || !isArguments(value))) + : false; + } + // fallback for IE + if (!isPlainObject(objectTypes)) { + isPlainObject = function(value, skipArgsCheck) { + // avoid non-objects and false positives for `arguments` objects + var result = false; + if (!(value && typeof value == 'object') || (!skipArgsCheck && isArguments(value))) { + return result; + } + // IE < 9 presents DOM nodes as `Object` objects except they have `toString` + // methods that are `typeof` "string" and still can coerce nodes to strings. + // Also check that the constructor is `Object` (i.e. `Object instanceof Object`) + var ctor = value.constructor; + if ((!noNodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) && + (!isFunction(ctor) || ctor instanceof ctor)) { + // IE < 9 iterates inherited properties before own properties. If the first + // iterated property is an object's own property then there are no inherited + // enumerable properties. + if (iteratesOwnLast) { + forIn(value, function(objValue, objKey) { + result = !hasOwnProperty.call(value, objKey); + return false; + }); + return result === 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(objValue, objKey) { + result = objKey; + }); + return result === false || hasOwnProperty.call(value, result); + } + return result; + }; + } /** - * Iterates over a `collection`, executing the `callback` for each value in the - * `collection`. The `callback` is bound to `thisArg` and invoked with 3 - * arguments; for arrays they are (value, index, array) and for objects they - * are (value, key, object). - * - * @static - * @memberOf _ - * @alias each - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array|Object} Returns the `collection`. - * @example - * - * _([1, 2, 3]).forEach(alert).join(','); - * // => alerts each number and returns '1,2,3' + * A shim implementation of `Object.keys` that produces an array of the given + * object's own enumerable property names. * - * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert); - * // => alerts each number (order is not guaranteed) + * @private + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names. */ - var forEach = createIterator(baseIteratorOptions, forEachIteratorOptions); + var shimKeys = createIterator({ + 'args': 'object', + 'init': '[]', + 'inLoop': 'result.push(index)' + }); + + /*--------------------------------------------------------------------------*/ /** - * Splits `collection` into sets, grouped by the result of running each value - * through `callback`. The `callback` is bound to `thisArg` and invoked with - * 3 arguments; for arrays they are (value, index, array) and for objects they - * are (value, key, object). The `callback` argument may also be the name of a - * property to group by. + * Creates a clone of `value`. If `deep` is `true`, all nested objects will + * also be cloned otherwise they will be assigned by reference. If a value has + * a `clone` method it will be used to perform the clone. Functions, DOM nodes, + * `arguments` objects, and objects created by constructors other than `Object` + * are **not** cloned unless they have a custom `clone` method. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function|String} callback The function called per iteration or - * property name to group by. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Object} Returns an object of grouped values. + * @category Objects + * @param {Mixed} value The value to clone. + * @param {Boolean} deep A flag to indicate a deep clone. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `deep`. + * @param {Array} [stack=[]] Internally used to keep track of traversed objects + * to avoid circular references. + * @param {Object} thorough Internally used to indicate whether or not to perform + * a more thorough clone of non-object values. + * @returns {Mixed} Returns the cloned `value`. * @example * - * _.groupBy([1.3, 2.1, 2.4], function(num) { return Math.floor(num); }); - * // => { '1': [1.3], '2': [2.1, 2.4] } + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 }, + * { 'name': 'curly', 'age': 60 } + * ]; + * + * _.clone({ 'name': 'moe' }); + * // => { 'name': 'moe' } * - * _.groupBy([1.3, 2.1, 2.4], function(num) { return this.floor(num); }, Math); - * // => { '1': [1.3], '2': [2.1, 2.4] } + * var shallow = _.clone(stooges); + * shallow[0] === stooges[0]; + * // => true * - * _.groupBy(['one', 'two', 'three'], 'length'); - * // => { '3': ['one', 'two'], '5': ['three'] } + * var deep = _.clone(stooges, true); + * shallow[0] === stooges[0]; + * // => false */ - var groupBy = createIterator(baseIteratorOptions, { - 'init': '{}', - 'top': - 'var prop, isFunc = typeof callback == \'function\';\n' + - 'if (isFunc && thisArg) callback = iteratorBind(callback, thisArg)', - 'inLoop': - 'prop = isFunc\n' + - ' ? callback(collection[index], index, collection)\n' + - ' : collection[index][callback];\n' + - '(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(collection[index])' - }); + function clone(value, deep, guard, stack, thorough) { + if (value == null) { + return value; + } + if (guard) { + deep = false; + } + // avoid slower checks on primitives + thorough || (thorough = { 'value': null }); + if (thorough.value == null) { + // primitives passed from iframes use the primary document's native prototypes + thorough.value = !!(BoolProto.clone || NumberProto.clone || StringProto.clone); + } + // use custom `clone` method if available + var isObj = objectTypes[typeof value]; + if ((isObj || thorough.value) && value.clone && isFunction(value.clone)) { + thorough.value = null; + return value.clone(deep); + } + // inspect [[Class]] + if (isObj) { + // don't clone `arguments` objects, functions, or non-object Objects + var className = toString.call(value); + if (!cloneableClasses[className] || (noArgsClass && isArguments(value))) { + return value; + } + var isArr = className == arrayClass; + isObj = isArr || (className == objectClass ? isPlainObject(value, true) : isObj); + } + // shallow clone + if (!isObj || !deep) { + // don't clone functions + return isObj + ? (isArr ? slice.call(value) : extend({}, value)) + : value; + } + + var ctor = value.constructor; + switch (className) { + case boolClass: + return new ctor(value == true); + + case dateClass: + return new ctor(+value); + + case numberClass: + case stringClass: + return new ctor(value); + + case regexpClass: + return ctor(value.source, reFlags.exec(value)); + } + + // check for circular references and return corresponding clone + stack || (stack = []); + var length = stack.length; + while (length--) { + if (stack[length].source == value) { + return stack[length].value; + } + } + + // init cloned object + length = value.length; + var result = isArr ? ctor(length) : {}; + + // add current clone and original source value to the stack of traversed objects + stack.push({ 'value': result, 'source': value }); + + // recursively populate clone (susceptible to call stack limits) + if (isArr) { + var index = -1; + while (++index < length) { + result[index] = clone(value[index], deep, null, stack, thorough); + } + } else { + forOwn(value, function(objValue, key) { + result[key] = clone(objValue, deep, null, stack, thorough); + }); + } + return result; + } /** - * Invokes the method named by `methodName` on each element in the `collection`. - * 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`. + * Assigns enumerable properties of the default object(s) to the `destination` + * object for all `destination` properties that resolve to `null`/`undefined`. + * Once a property is set, additional defaults of the same property will be + * ignored. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} 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 values returned from each invoked method. + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [default1, default2, ...] The default objects. + * @returns {Object} Returns the destination object. * @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']] + * var iceCream = { 'flavor': 'chocolate' }; + * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'rainbow' }); + * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' } */ - var invoke = createIterator(mapIteratorOptions, { - 'args': 'collection, methodName', - 'top': - 'var args = slice.call(arguments, 2),\n' + - ' isFunc = typeof methodName == \'function\'', - 'inLoop': { - 'array': 'result[index] = (isFunc ? methodName : collection[index][methodName]).apply(collection[index], args)', - 'object': 'result.push((isFunc ? methodName : collection[index][methodName]).apply(collection[index], args))' - } + var defaults = createIterator(extendIteratorOptions, { + 'inLoop': 'if (result[index] == null) ' + extendIteratorOptions.inLoop }); /** - * Produces a new array of values by mapping each element in the `collection` - * through a transformation `callback`. The `callback` is bound to `thisArg` - * and invoked with 3 arguments; for arrays they are (value, index, array) - * and for objects they are (value, key, 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 `callback` is passed, it will be executed for each property + * in the `object`, dropping the properties `callback` returns truthy for. The + * `callback` is bound to `thisArg` and invoked with 3 arguments; (value, key, object). * * @static * @memberOf _ - * @alias collect - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. + * @alias omit + * @category Objects + * @param {Object} object The source object. + * @param {Function|String} callback|[prop1, prop2, ...] The properties to drop + * or the function called per iteration. * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of values returned by the callback. + * @returns {Object} Returns an object without the dropped properties. * @example * - * _.map([1, 2, 3], function(num) { return num * 3; }); - * // => [3, 6, 9] + * _.drop({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'userid'); + * // => { 'name': 'moe', 'age': 40 } * - * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; }); - * // => [3, 6, 9] (order is not guaranteed) + * _.drop({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) { + * return key.charAt(0) == '_'; + * }); + * // => { 'name': 'moe' } */ - var map = createIterator(baseIteratorOptions, mapIteratorOptions); + var drop = createIterator(dropIteratorOptions); /** - * Retrieves the value of a specified property from all elements in - * the `collection`. + * Assigns enumerable properties of the source object(s) to the `destination` + * object. Subsequent sources will overwrite propery assignments of previous + * sources. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {String} property The property to pluck. - * @returns {Array} Returns a new array of property values. + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @returns {Object} Returns the destination object. * @example * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 }, - * { 'name': 'curly', 'age': 60 } - * ]; - * - * _.pluck(stooges, 'name'); - * // => ['moe', 'larry', 'curly'] + * _.extend({ 'name': 'moe' }, { 'age': 40 }); + * // => { 'name': 'moe', 'age': 40 } */ - var pluck = createIterator(mapIteratorOptions, { - 'args': 'collection, property', - 'inLoop': { - 'array': 'result[index] = collection[index][property]', - 'object': 'result.push(collection[index][property])' - } - }); + var extend = createIterator(extendIteratorOptions); /** - * Boils down a `collection` to a single value. The initial state of the - * reduction is `accumulator` and each successive step of it should be returned - * by the `callback`. The `callback` is bound to `thisArg` and invoked with 4 - * arguments; for arrays they are (accumulator, value, index, array) and for - * objects they are (accumulator, value, key, object). + * Iterates over `object`'s own and inherited enumerable properties, executing + * the `callback` for each property. The `callback` is bound to `thisArg` and + * invoked with 3 arguments; (value, key, object). Callbacks may exit iteration + * early by explicitly returning `false`. * * @static * @memberOf _ - * @alias foldl, inject - * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @category Objects + * @param {Object} object The object to iterate over. * @param {Function} callback The function called per iteration. - * @param {Mixed} [accumulator] Initial value of the accumulator. * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the accumulated value. + * @returns {Object} Returns `object`. * @example * - * var sum = _.reduce([1, 2, 3], function(memo, num) { return memo + num; }); - * // => 6 + * 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 reduce = createIterator({ - 'args': 'collection, callback, accumulator, thisArg', - 'init': 'accumulator', - 'top': - 'var noaccum = arguments.length < 3;\n' + - 'if (thisArg) callback = iteratorBind(callback, thisArg)', - 'beforeLoop': { - 'array': 'if (noaccum) result = collection[++index]' - }, - 'inLoop': { - 'array': - 'result = callback(result, collection[index], index, collection)', - 'object': - 'result = noaccum\n' + - ' ? (noaccum = false, collection[index])\n' + - ' : callback(result, collection[index], index, collection)' - } + var forIn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions, { + 'useHas': false }); /** - * The right-associative version of `_.reduce`. + * Iterates over `object`'s own enumerable properties, executing the `callback` + * for each property. The `callback` is bound to `thisArg` and invoked with 3 + * arguments; (value, key, object). Callbacks may exit iteration early by + * explicitly returning `false`. * * @static * @memberOf _ - * @alias foldr - * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @category Objects + * @param {Object} object The object to iterate over. * @param {Function} callback The function called per iteration. - * @param {Mixed} [accumulator] Initial value of the accumulator. * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the accumulated value. + * @returns {Object} Returns `object`. * @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] + * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { + * alert(key); + * }); + * // => alerts '0', '1', and 'length' (order is not guaranteed) */ - function reduceRight(collection, callback, accumulator, thisArg) { - if (!collection) { - return accumulator; - } - - var length = collection.length, - noaccum = arguments.length < 3; - - if(thisArg) { - callback = iteratorBind(callback, thisArg); - } - if (length === length >>> 0) { - if (length && noaccum) { - accumulator = collection[--length]; - } - while (length--) { - accumulator = callback(accumulator, collection[length], length, collection); - } - return accumulator; - } - - var prop, - props = keys(collection); - - length = props.length; - if (length && noaccum) { - accumulator = collection[props[--length]]; - } - while (length--) { - prop = props[length]; - accumulator = callback(accumulator, collection[prop], prop, collection); - } - return accumulator; - } + var forOwn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions); /** - * The opposite of `_.filter`, this method returns the values of a `collection` - * that `callback` does **not** return truthy for. + * Creates a sorted array of all enumerable properties, own and inherited, + * of `object` that have function values. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of values that did **not** pass the callback check. + * @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 * - * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); - * // => [1, 3, 5] + * _.functions(_); + * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] */ - var reject = createIterator(baseIteratorOptions, filterIteratorOptions, { - 'inLoop': '!' + filterIteratorOptions.inLoop + var functions = createIterator({ + 'useHas': false, + 'args': 'object', + 'init': '[]', + 'inLoop': 'if (isFunction(value)) result.push(index)', + 'bottom': 'result.sort()' }); /** - * 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 3 arguments; for arrays they are - * (value, index, array) and for objects they are (value, key, object). + * Checks if the specified object `property` exists and is a direct property, + * instead of an inherited property. * * @static * @memberOf _ - * @alias any - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Boolean} Returns `true` if any value passes the callback check, else `false`. + * @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 * - * _.some([null, 0, 'yes', false]); + * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); * // => true */ - var some = createIterator(baseIteratorOptions, everyIteratorOptions, { - 'init': 'false', - 'inLoop': everyIteratorOptions.inLoop.replace('!', '') - }); - + function has(object, property) { + return object ? hasOwnProperty.call(object, property) : false; + } /** - * Produces a new sorted array, ranked in ascending order by the results of - * running each element of `collection` through a transformation `callback`. - * The `callback` is bound to `thisArg` and invoked with 3 arguments; - * for arrays they are (value, index, array) and for objects they are - * (value, key, object). The `callback` argument may also be the name of a - * property to sort by (e.g. 'length'). + * Checks if `value` is a boolean (`true` or `false`) value. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function|String} callback The function called per iteration or - * property name to sort by. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of sorted values. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a boolean value, else `false`. * @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] - * - * _.sortBy(['larry', 'brendan', 'moe'], 'length'); - * // => ['moe', 'larry', 'brendan'] + * _.isBoolean(null); + * // => false */ - function sortBy(collection, callback, thisArg) { - if (typeof callback == 'string') { - var prop = callback; - callback = function(collection) { return collection[prop]; }; - } else if (thisArg) { - callback = iteratorBind(callback, thisArg); - } - var result = toSortable(collection, callback).sort(compareAscending), - length = result.length; - - while (length--) { - result[length] = result[length].value; - } - return result; + function isBoolean(value) { + return value === true || value === false || toString.call(value) == boolClass; } /** - * Converts the `collection`, into an array. Useful for converting the - * `arguments` object. + * Checks if `value` is a date. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to convert. - * @returns {Array} Returns the new converted array. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a date, else `false`. * @example * - * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4); - * // => [2, 3, 4] + * _.isDate(new Date); + * // => true */ - function toArray(collection) { - if (!collection) { - return []; - } - if (collection.toArray && toString.call(collection.toArray) == funcClass) { - return collection.toArray(); - } - var length = collection.length; - if (length === length >>> 0) { - return slice.call(collection); - } - return values(collection); + function isDate(value) { + return toString.call(value) == dateClass; } - /*--------------------------------------------------------------------------*/ - /** - * Produces a new array with all falsey values of `array` removed. The values - * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey. + * Checks if `value` is a DOM element. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to compact. - * @returns {Array} Returns a new filtered array. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a DOM element, else `false`. * @example * - * _.compact([0, 1, false, 2, '', 3]); - * // => [1, 2, 3] + * _.isElement(document.body); + * // => true */ - function compact(array) { - var result = []; - if (!array) { - return result; - } - var index = -1, - length = array.length; + function isElement(value) { + return value ? value.nodeType === 1 : false; + } - while (++index < length) { - if (array[index]) { - result.push(array[index]); - } + /** + * 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 + */ + var isEmpty = createIterator({ + 'args': 'value', + 'init': 'true', + 'top': + 'var className = toString.call(value),\n' + + ' length = value.length;\n' + + 'if (arrayLikeClasses[className]' + + (noArgsClass ? ' || isArguments(value)' : '') + ' ||\n' + + ' (className == objectClass && length > -1 && length === length >>> 0 &&\n' + + ' isFunction(value.splice))' + + ') return !length', + 'inLoop': { + 'object': 'return false' } - return result; - } + }); /** - * Produces a new array of `array` values not present in the other arrays - * using strict equality for comparisons, i.e. `===`. + * Performs a deep comparison between two values to determine if they are + * equivalent to each other. If a value has an `isEqual` method it will be + * used to perform the comparison. * * @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` values not present in the - * other arrays. + * @category Objects + * @param {Mixed} a The value to compare. + * @param {Mixed} b The other value to compare. + * @param {Array} [stack=[]] Internally used to keep track of traversed objects + * to avoid circular references. + * @param {Object} thorough Internally used to indicate whether or not to perform + * a more thorough comparison of non-object values. + * @returns {Boolean} Returns `true` if the values are equvalent, else `false`. * @example * - * _.difference([1, 2, 3, 4, 5], [5, 2, 10]); - * // => [1, 3, 4] + * var moe = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; + * var clone = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; + * + * moe == clone; + * // => false + * + * _.isEqual(moe, clone); + * // => true */ - function difference(array) { - var result = []; - if (!array) { - return result; + function isEqual(a, b, stack, thorough) { + // a strict comparison is necessary because `null == undefined` + if (a == null || b == null) { + return a === b; + } + // avoid slower checks on non-objects + thorough || (thorough = { 'value': null }); + if (thorough.value == null) { + // primitives passed from iframes use the primary document's native prototypes + thorough.value = !!(BoolProto.isEqual || NumberProto.isEqual || StringProto.isEqual); + } + if (objectTypes[typeof a] || objectTypes[typeof b] || thorough.value) { + // unwrap any LoDash wrapped values + if (a._chain) { + a = a._wrapped; + } + if (b._chain) { + b = b._wrapped; + } + // use custom `isEqual` method if available + if (a.isEqual && isFunction(a.isEqual)) { + thorough.value = null; + return a.isEqual(b); + } + if (b.isEqual && isFunction(b.isEqual)) { + thorough.value = null; + return b.isEqual(a); + } + } + // exit early for identical values + if (a === b) { + // treat `+0` vs. `-0` as not equal + return a !== 0 || (1 / a == 1 / b); + } + // compare [[Class]] names + var className = toString.call(a); + if (className != toString.call(b)) { + 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 == b + ''; + } + // exit early, in older browsers, if `a` is array-like but not `b` + var isArr = arrayLikeClasses[className]; + if (noArgsClass && !isArr && (isArr = isArguments(a)) && !isArguments(b)) { + return false; + } + // exit for functions and DOM nodes + if (!isArr && (className != objectClass || (noNodeClass && ( + (typeof a.toString != 'function' && typeof (a + '') == 'string') || + (typeof b.toString != 'function' && typeof (b + '') == 'string'))))) { + 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) + stack || (stack = []); + var length = stack.length; + while (length--) { + if (stack[length] == a) { + return true; + } } + var index = -1, - length = array.length, - flattened = concat.apply(result, arguments); + result = true, + size = 0; - while (++index < length) { - if (indexOf(flattened, array[index], length) < 0) { - result.push(array[index]); + // add `a` to the stack of traversed objects + stack.push(a); + + // recursively compare objects and arrays (susceptible to call stack limits) + if (isArr) { + // compare lengths to determine if a deep comparison is necessary + size = a.length; + result = size == b.length; + + if (result) { + // deep compare the contents, ignoring non-numeric properties + while (size--) { + if (!(result = isEqual(a[size], b[size], stack, thorough))) { + break; + } + } } + return result; } - return result; + + 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; + } + // deep compare objects + for (var prop in a) { + if (hasOwnProperty.call(a, prop)) { + // count the number of properties. + size++; + // deep compare each property value. + if (!(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack, thorough))) { + return false; + } + } + } + // ensure both objects have the same number of properties + for (prop in b) { + // The JS engine in Adobe products, like InDesign, has a bug that causes + // `!size--` to throw an error so it must be wrapped in parentheses. + // https://github.com/documentcloud/underscore/issues/355 + if (hasOwnProperty.call(b, prop) && !(size--)) { + // `size` will be `-1` if `b` has more properties than `a` + return false; + } + } + // handle JScript [[DontEnum]] bug + if (hasDontEnumBug) { + while (++index < 7) { + prop = shadowed[index]; + if (hasOwnProperty.call(a, prop) && + !(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack, thorough))) { + return false; + } + } + } + return true; } /** - * Gets the first value of the `array`. Pass `n` to return the first `n` values - * of the `array`. + * Checks if `value` is a finite number. + * + * Note: This is not the same as native `isFinite`, which will return true for + * booleans and other values. See http://es5.github.com/#x15.1.2.5. * + * @deprecated * @static * @memberOf _ - * @alias head, take - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Mixed} Returns the first value or an array of the first `n` values - * of `array`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a finite number, else `false`. * @example * - * _.first([5, 4, 3, 2, 1]); - * // => 5 + * _.isFinite(-101); + * // => true + * + * _.isFinite('10'); + * // => false + * + * _.isFinite(Infinity); + * // => false */ - function first(array, n, guard) { - if (array) { - return (n == null || guard) ? array[0] : slice.call(array, 0, n); - } + function isFinite(value) { + return nativeIsFinite(value) && toString.call(value) == numberClass; } /** - * Flattens a nested array (the nesting can be to any depth). If `shallow` is - * truthy, `array` will only be flattened a single level. + * Checks if `value` is the language type of Object. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to compact. - * @param {Boolean} shallow A flag to indicate only flattening a single level. - * @returns {Array} Returns a new flattened array. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is an object, else `false`. * @example * - * _.flatten([1, [2], [3, [[4]]]]); - * // => [1, 2, 3, 4]; + * _.isObject({}); + * // => true * - * _.flatten([1, [2], [3, [[4]]]], true); - * // => [1, 2, 3, [[4]]]; + * _.isObject(1); + * // => false */ - function flatten(array, shallow) { - var result = []; - if (!array) { - return result; - } - var value, - index = -1, - length = array.length; - - while (++index < length) { - value = array[index]; - if (isArray(value)) { - push.apply(result, shallow ? value : flatten(value)); - } else { - result.push(value); - } - } - return result; + 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] : false; } /** - * 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 `isSorted` will run a faster binary search. + * 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. * + * @deprecated * @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 start searching from or - * `true` to perform a binary search on a sorted `array`. - * @returns {Number} Returns the index of the matched value or `-1`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is `NaN`, else `false`. * @example * - * _.indexOf([1, 2, 3, 1, 2, 3], 2); - * // => 1 + * _.isNaN(NaN); + * // => true * - * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3); - * // => 4 + * _.isNaN(new Number(NaN)); + * // => true * - * _.indexOf([1, 1, 2, 2, 3, 3], 2, true); - * // => 2 + * isNaN(undefined); + * // => true + * + * _.isNaN(undefined); + * // => false */ - function indexOf(array, value, fromIndex) { - if (!array) { - return -1; - } - var index = -1, - length = array.length; - - if (fromIndex) { - if (typeof fromIndex == 'number') { - index = (fromIndex < 0 ? Math.max(0, length + fromIndex) : fromIndex) - 1; - } else { - index = sortedIndex(array, value); - return array[index] === value ? index : -1; - } - } - while (++index < length) { - if (array[index] === value) { - return index; - } - } - return -1; + 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 toString.call(value) == numberClass && value != +value } /** - * Gets all but the last value of `array`. Pass `n` to exclude the last `n` - * values from the result. + * Checks if `value` is `null`. * + * @deprecated * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Array} Returns all but the last value or `n` values of `array`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is `null`, else `false`. * @example * - * _.initial([3, 2, 1]); - * // => [3, 2] + * _.isNull(null); + * // => true + * + * _.isNull(undefined); + * // => false */ - function initial(array, n, guard) { - if (!array) { - return []; - } - return slice.call(array, 0, -((n == null || guard) ? 1 : n)); + function isNull(value) { + return value === null; } /** - * Computes the intersection of all the passed-in arrays. + * Checks if `value` is a number. * * @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 **all** of the arrays. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a number, else `false`. * @example * - * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); - * // => [1, 2] + * _.isNumber(8.4 * 5; + * // => true */ - function intersection(array) { - var result = []; - if (!array) { - return result; - } - var value, - index = -1, - length = array.length, - others = slice.call(arguments, 1); - - while (++index < length) { - value = array[index]; - if (indexOf(result, value) < 0 && - every(others, function(other) { return indexOf(other, value) > -1; })) { - result.push(value); - } - } - return result; + function isNumber(value) { + return toString.call(value) == numberClass; } /** - * Gets the last value of the `array`. Pass `n` to return the lasy `n` values - * of the `array`. + * Checks if `value` is a regular expression. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Mixed} Returns the last value or an array of the last `n` values - * of `array`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a regular expression, else `false`. * @example * - * _.last([3, 2, 1]); - * // => 1 + * _.isRegExp(/moe/); + * // => true */ - function last(array, n, guard) { - if (array) { - var length = array.length; - return (n == null || guard) ? array[length - 1] : slice.call(array, -n || length); - } + function isRegExp(value) { + return toString.call(value) == regexpClass; } /** - * Gets the index at which the last occurrence of `value` is found using - * strict equality for comparisons, i.e. `===`. + * Checks if `value` is a string. * * @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 start searching from. - * @returns {Number} Returns the index of the matched value or `-1`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a string, else `false`. * @example * - * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); - * // => 4 - * - * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3); - * // => 1 + * _.isString('moe'); + * // => true */ - function lastIndexOf(array, value, fromIndex) { - if (!array) { - return -1; - } - var index = array.length; - if (fromIndex && typeof fromIndex == 'number') { - index = (fromIndex < 0 ? Math.max(0, index + fromIndex) : Math.min(fromIndex, index - 1)) + 1; - } - while (index--) { - if (array[index] === value) { - return index; - } - } - return -1; + function isString(value) { + return toString.call(value) == stringClass; } /** - * 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 3 arguments; (value, index, array). + * Checks if `value` is `undefined`. * + * @deprecated * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to iterate over. - * @param {Function} [callback] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the maximum value. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is `undefined`, else `false`. * @example * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 }, - * { 'name': 'curly', 'age': 60 } - * ]; - * - * _.max(stooges, function(stooge) { return stooge.age; }); - * // => { 'name': 'curly', 'age': 60 }; + * _.isUndefined(void 0); + * // => true */ - function max(array, callback, thisArg) { - var computed = -Infinity, - result = computed; - - if (!array) { - return result; - } - var current, - index = -1, - length = array.length; - - if (!callback) { - while (++index < length) { - if (array[index] > result) { - result = array[index]; - } - } - return result; - } - if (thisArg) { - callback = iteratorBind(callback, thisArg); - } - while (++index < length) { - current = callback(array[index], index, array); - if (current > computed) { - computed = current; - result = array[index]; - } - } - return result; + function isUndefined(value) { + return value === undefined; } /** - * 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 3 arguments; (value, index, array). + * Creates an array composed of the own enumerable property names of `object`. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to iterate over. - * @param {Function} [callback] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the minimum value. + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names. * @example * - * _.min([10, 5, 100, 2, 1000]); - * // => 2 + * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); + * // => ['one', 'two', 'three'] (order is not guaranteed) */ - function min(array, callback, thisArg) { - var computed = Infinity, - result = computed; - - if (!array) { - return result; - } - var current, - index = -1, - length = array.length; + var keys = !nativeKeys ? shimKeys : function(object) { + var type = typeof object; - if (!callback) { - while (++index < length) { - if (array[index] < result) { - result = array[index]; - } - } - return result; - } - if (thisArg) { - callback = iteratorBind(callback, thisArg); - } - while (++index < length) { - current = callback(array[index], index, array); - if (current < computed) { - computed = current; - result = array[index]; - } + // avoid iterating over the `prototype` property + if (type == 'function' && propertyIsEnumerable.call(object, 'prototype')) { + return shimKeys(object); } - return result; - } + return object && objectTypes[type] + ? nativeKeys(object) + : []; + }; /** - * Creates an array of numbers (positive and/or negative) progressing from - * `start` up to but not including `stop`. This method is a port of Python's - * `range()` function. See http://docs.python.org/library/functions.html#range. + * Merges enumerable properties of the source object(s) into the `destination` + * object. Subsequent sources will overwrite propery assignments of previous + * sources. * * @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 descrement by. - * @returns {Array} Returns a new range array. + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @param {Object} [indicator] Internally used to indicate that the `stack` + * argument is an array of traversed objects instead of another source object. + * @param {Array} [stack=[]] Internally used to keep track of traversed objects + * to avoid circular references. + * @returns {Object} Returns the destination object. * @example * - * _.range(10); - * // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + * var stooges = [ + * { 'name': 'moe' }, + * { 'name': 'larry' } + * ]; * - * _.range(1, 11); - * // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + * var ages = [ + * { 'age': 40 }, + * { 'age': 50 } + * ]; * - * _.range(0, 30, 5); - * // => [0, 5, 10, 15, 20, 25] + * _.merge(stooges, ages); + * // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] + */ + var merge = createIterator(extendIteratorOptions, { + 'args': 'object, source, indicator, stack', + 'top': + 'var destValue, found, isArr, stackLength, recursive = indicator == isPlainObject;\n' + + 'if (!recursive) stack = [];\n' + + 'for (var argsIndex = 1, argsLength = recursive ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n' + + ' if (iteratee = arguments[argsIndex]) {', + 'inLoop': + 'if (value && ((isArr = isArray(value)) || isPlainObject(value))) {\n' + + ' found = false; stackLength = stack.length;\n' + + ' while (stackLength--) {\n' + + ' if (found = stack[stackLength].source == value) break\n' + + ' }\n' + + ' if (found) {\n' + + ' result[index] = stack[stackLength].value\n' + + ' } else {\n' + + ' destValue = (destValue = result[index]) && isArr\n' + + ' ? (isArray(destValue) ? destValue : [])\n' + + ' : (isPlainObject(destValue) ? destValue : {});\n' + + ' stack.push({ value: destValue, source: value });\n' + + ' result[index] = callee(destValue, value, isPlainObject, stack)\n' + + ' }\n' + + '} else if (value != null) {\n' + + ' result[index] = value\n' + + '}' + }); + + /** + * 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 3 arguments; (value, key, object). * - * _.range(0, -10, -1); - * // => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The source object. + * @param {Function|String} callback|[prop1, prop2, ...] The properties to pick + * or the function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Object} Returns an object composed of the picked properties. + * @example * - * _.range(0); - * // => [] + * _.pick({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'name', 'age'); + * // => { 'name': 'moe', 'age': 40 } + * + * _.pick({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) { + * return key.charAt(0) != '_'; + * }); + * // => { 'name': 'moe' } */ - function range(start, end, step) { - step || (step = 1); - if (end == null) { - end = start || 0; - start = 0; + var pick = createIterator(dropIteratorOptions, { + 'top': + 'if (typeof callback != \'function\') {\n' + + ' var prop,\n' + + ' props = concat.apply(ArrayProto, arguments),\n' + + ' length = props.length;\n' + + ' for (index = 1; index < length; index++) {\n' + + ' prop = props[index];\n' + + ' if (prop in object) result[prop] = object[prop]\n' + + ' }\n' + + '} else {\n' + + ' if (thisArg) callback = iteratorBind(callback, thisArg)', + 'inLoop': + 'if (callback(value, index, object)) result[index] = value', + 'bottom': '}' + }); + + /** + * Gets the size of `value` by returning `value.length` if `value` is an + * array, string, or `arguments` object. If `value` is an object, size is + * determined by returning the number of own enumerable properties it has. + * + * @static + * @memberOf _ + * @category Objects + * @param {Array|Object|String} value The value to inspect. + * @returns {Number} Returns `value.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(value) { + if (!value) { + return 0; } - // use `Array(length)` so V8 will avoid the slower "dictionary" mode - // http://www.youtube.com/watch?v=XAqIpGU8ZZk#t=16m27s - var index = -1, - length = Math.max(0, Math.ceil((end - start) / step)), - result = Array(length); + var className = toString.call(value), + length = value.length; - while (++index < length) { - result[index] = start; - start += step; + // return `value.length` for `arguments` objects, arrays, strings, and DOM + // query collections of libraries like jQuery and MooTools + // http://code.google.com/p/fbug/source/browse/branches/firebug1.9/content/firebug/chrome/reps.js?r=12614#653 + // http://trac.webkit.org/browser/trunk/Source/WebCore/inspector/InjectedScriptSource.js?rev=125186#L609 + if (arrayLikeClasses[className] || (noArgsClass && isArguments(value)) || + (className == objectClass && length > -1 && length === length >>> 0 && isFunction(value.splice))) { + return length; } - return result; + return keys(value).length; } /** - * The opposite of `_.initial`, this method gets all but the first value of - * `array`. Pass `n` to exclude the first `n` values from the result. + * Creates an array composed of the own enumerable property values of `object`. * * @static * @memberOf _ - * @alias tail - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Array} Returns all but the first value or `n` values of `array`. + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property values. * @example * - * _.rest([3, 2, 1]); - * // => [2, 1] + * _.values({ 'one': 1, 'two': 2, 'three': 3 }); + * // => [1, 2, 3] */ - function rest(array, n, guard) { - if (!array) { - return []; - } - return slice.call(array, (n == null || guard) ? 1 : n); - } + var values = createIterator({ + 'args': 'object', + 'init': '[]', + 'inLoop': 'result.push(value)' + }); + + /*--------------------------------------------------------------------------*/ /** - * Produces a new array of shuffled `array` values, using a version of the - * Fisher-Yates shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. + * Checks if a given `target` element is present in a `collection` using strict + * equality for comparisons, i.e. `===`. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to shuffle. - * @returns {Array} Returns a new shuffled array. + * @alias include + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Mixed} target The value to check for. + * @returns {Boolean} Returns `true` if the `target` element is found, else `false`. * @example * - * _.shuffle([1, 2, 3, 4, 5, 6]); - * // => [4, 1, 6, 3, 5, 2] + * _.contains([1, 2, 3], 3); + * // => true + * + * _.contains({ 'name': 'moe', 'age': 40 }, 'moe'); + * // => true + * + * _.contains('curly', 'ur'); + * // => true */ - function shuffle(array) { - if (!array) { - return []; - } - var rand, - index = -1, - length = array.length, - result = Array(length); + var contains = createIterator({ + 'args': 'collection, target', + 'init': 'false', + 'noCharByIndex': false, + 'beforeLoop': { + 'array': 'if (toString.call(collection) == stringClass) return collection.indexOf(target) > -1' + }, + 'inLoop': 'if (value === target) return true' + }); - while (++index < length) { - rand = Math.floor(Math.random() * (index + 1)); - result[index] = result[rand]; - result[rand] = array[index]; - } - return result; - } + /** + * Creates an object composed of keys returned from running each element of + * `collection` through a `callback`. The corresponding value of each key is + * the number of times the key was returned by `callback`. The `callback` is + * bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection). + * The `callback` argument may also be the name of a property to count by (e.g. 'length'). + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|String} callback|property The function called per iteration + * or property name to count by. + * @param {Mixed} [thisArg] The `this` binding for the 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 } + */ + var countBy = createIterator(baseIteratorOptions, countByIteratorOptions); /** - * 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 1 argument; (value). + * Checks if the `callback` returns a truthy value for **all** elements of a + * `collection`. The `callback` is bound to `thisArg` and invoked with 3 + * arguments; (value, index|key, collection). * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to iterate over. - * @param {Mixed} value The value to evaluate. + * @alias all + * @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 for the callback. - * @returns {Number} Returns the index at which the value should be inserted - * into `array`. + * @returns {Boolean} Returns `true` if all elements pass the callback check, else `false`. * @example * - * _.sortedIndex([20, 30, 40], 35); - * // => 2 + * _.every([true, 1, null, 'yes'], Boolean); + * // => false + */ + var every = createIterator(baseIteratorOptions, everyIteratorOptions); + + /** + * 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 3 arguments; (value, index|key, collection). * - * var dict = { - * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'thirty-five': 35, 'fourty': 40 } - * }; + * @static + * @memberOf _ + * @alias select + * @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 for the callback. + * @returns {Array} Returns a new array of elements that passed callback check. + * @example * - * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { - * return dict.wordToNumber[word]; - * }); - * // => 2 + * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [2, 4, 6] + */ + var filter = createIterator(baseIteratorOptions, filterIteratorOptions); + + /** + * Examines each element in a `collection`, returning the first one the `callback` + * returns truthy for. The function returns as soon as it finds an acceptable + * element, and does not iterate over the entire `collection`. The `callback` is + * bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection). * - * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { - * return this.wordToNumber[word]; - * }, dict); + * @static + * @memberOf _ + * @alias detect + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the element that passed the callback check, else `undefined`. + * @example + * + * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); * // => 2 */ - function sortedIndex(array, value, callback, thisArg) { - if (!array) { - return 0; + var find = createIterator(baseIteratorOptions, forEachIteratorOptions, { + 'init': '', + 'inLoop': 'if (callback(value, index, collection)) return value' + }); + + /** + * Iterates over a `collection`, executing the `callback` for each element in + * the `collection`. The `callback` is bound to `thisArg` and invoked with 3 + * 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 The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Array|Object} 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 (order is not guaranteed) + */ + var forEach = createIterator(baseIteratorOptions, forEachIteratorOptions); + + /** + * Creates an object composed of keys returned from running each element of + * `collection` through a `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 3 arguments; (value, index|key, collection). + * The `callback` argument may also be the name of a property to count by (e.g. 'length'). + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|String} callback|property The function called per iteration + * or property name to group by. + * @param {Mixed} [thisArg] The `this` binding for the 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] } + * + * _.groupBy(['one', 'two', 'three'], 'length'); + * // => { '3': ['one', 'two'], '5': ['three'] } + */ + var groupBy = createIterator(baseIteratorOptions, countByIteratorOptions, { + 'inLoop': + 'prop = callback(value, index, collection);\n' + + '(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(value)' + }); + + /** + * Invokes the method named by `methodName` on each element in the `collection`. + * 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 values returned from 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']] + */ + var invoke = createIterator(mapIteratorOptions, { + 'args': 'collection, methodName', + 'top': + 'var args = slice.call(arguments, 2),\n' + + ' isFunc = typeof methodName == \'function\'', + 'inLoop': { + 'array': + 'result[index] = (isFunc ? methodName : value[methodName]).apply(value, args)', + 'object': + 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + + '((isFunc ? methodName : value[methodName]).apply(value, args))' } - var mid, - low = 0, - high = array.length; + }); - if (callback) { - if (thisArg) { - callback = bind(callback, thisArg); - } - value = callback(value); - while (low < high) { - mid = (low + high) >>> 1; - callback(array[mid]) < value ? low = mid + 1 : high = mid; + /** + * Creates a new array of values by running each element in the `collection` + * through a `callback`. The `callback` is bound to `thisArg` and invoked with + * 3 arguments; (value, index|key, collection). + * + * @static + * @memberOf _ + * @alias collect + * @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 for the callback. + * @returns {Array} Returns a new array of elements returned by the callback. + * @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 map = createIterator(baseIteratorOptions, mapIteratorOptions); + + /** + * Retrieves the value of a specified property from all elements in + * the `collection`. + * + * @static + * @memberOf _ + * @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 }, + * { 'name': 'curly', 'age': 60 } + * ]; + * + * _.pluck(stooges, 'name'); + * // => ['moe', 'larry', 'curly'] + */ + var pluck = createIterator(mapIteratorOptions, { + 'args': 'collection, property', + 'inLoop': { + 'array': 'result[index] = value[property]', + 'object': 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '(value[property])' + } + }); + + /** + * Boils down a `collection` to a single value. The initial state of the + * reduction is `accumulator` and each successive step of it should be returned + * by the `callback`. The `callback` is bound to `thisArg` and invoked with 4 + * arguments; for arrays they are (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 The function called per iteration. + * @param {Mixed} [accumulator] Initial value of the accumulator. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the accumulated value. + * @example + * + * var sum = _.reduce([1, 2, 3], function(memo, num) { return memo + num; }); + * // => 6 + */ + var reduce = createIterator({ + 'args': 'collection, callback, accumulator, thisArg', + 'init': 'accumulator', + 'top': + 'var noaccum = arguments.length < 3;\n' + + 'if (thisArg) callback = iteratorBind(callback, thisArg)', + 'beforeLoop': { + 'array': 'if (noaccum) result = iteratee[++index]' + }, + 'inLoop': { + 'array': + 'result = callback(result, value, index, collection)', + 'object': + 'result = noaccum\n' + + ' ? (noaccum = false, value)\n' + + ' : callback(result, value, index, collection)' + } + }); + + /** + * The right-associative version of `_.reduce`. + * + * @static + * @memberOf _ + * @alias foldr + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} [accumulator] Initial value of the accumulator. + * @param {Mixed} [thisArg] The `this` binding for the 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) { + if (!collection) { + return accumulator; + } + + var length = collection.length, + noaccum = arguments.length < 3; + + if(thisArg) { + callback = iteratorBind(callback, thisArg); + } + // Opera 10.53-10.60 JITted `length >>> 0` returns the wrong value for negative numbers + if (length > -1 && length === length >>> 0) { + var iteratee = noCharByIndex && toString.call(collection) == stringClass + ? collection.split('') + : collection; + + if (length && noaccum) { + accumulator = iteratee[--length]; } - } else { - while (low < high) { - mid = (low + high) >>> 1; - array[mid] < value ? low = mid + 1 : high = mid; + while (length--) { + accumulator = callback(accumulator, iteratee[length], length, collection); } + return accumulator; } - return low; + + var prop, + props = keys(collection); + + length = props.length; + if (length && noaccum) { + accumulator = collection[props[--length]]; + } + while (length--) { + prop = props[length]; + accumulator = callback(accumulator, collection[prop], prop, collection); + } + return accumulator; } /** - * Computes the union of the passed-in arrays. + * The opposite of `_.filter`, this method returns the values of a + * `collection` that `callback` does **not** return truthy for. * * @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. + * @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 for the callback. + * @returns {Array} Returns a new array of elements that did **not** pass the callback check. * @example * - * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); - * // => [1, 2, 3, 101, 10] + * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [1, 3, 5] */ - function union() { - var index = -1, - result = [], - flattened = concat.apply(result, arguments), - length = flattened.length; - - while (++index < length) { - if (indexOf(result, flattened[index]) < 0) { - result.push(flattened[index]); - } - } - return result; - } + var reject = createIterator(baseIteratorOptions, filterIteratorOptions, { + 'inLoop': '!' + filterIteratorOptions.inLoop + }); /** - * Produces 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 value of `array` is passed through a transformation `callback` before - * uniqueness is computed. The `callback` is bound to `thisArg` and invoked - * with 3 arguments; (value, index, array). + * 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 3 arguments; (value, index|key, collection). * * @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. + * @alias any + * @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 for the callback. - * @returns {Array} Returns a duplicate-value-free array. + * @returns {Boolean} Returns `true` if any element passes the callback check, else `false`. * @example * - * _.uniq([1, 2, 1, 3, 1]); - * // => [1, 2, 3] + * _.some([null, 0, 'yes', false]); + * // => true + */ + var some = createIterator(baseIteratorOptions, everyIteratorOptions, { + 'init': 'false', + 'inLoop': everyIteratorOptions.inLoop.replace('!', '') + }); + + /** + * Creates a new array, stable sorted in ascending order by the results of + * running each element of `collection` through a `callback`. The `callback` + * is bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection). + * The `callback` argument may also be the name of a property to sort by (e.g. 'length'). * - * _.uniq([1, 1, 2, 2, 3], true); - * // => [1, 2, 3] + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|String} callback|property The function called per iteration + * or property name to sort by. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Array} Returns a new array of sorted elements. + * @example * - * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); }); - * // => [1, 2, 3] + * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); }); + * // => [3, 1, 2] * - * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math); - * // => [1, 2, 3] + * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math); + * // => [3, 1, 2] + * + * _.sortBy(['larry', 'brendan', 'moe'], 'length'); + * // => ['moe', 'larry', 'brendan'] */ - function uniq(array, isSorted, callback, thisArg) { - var result = []; - if (!array) { - return result; - } - var computed, - index = -1, - length = array.length, - seen = []; - - // juggle arguments - if (typeof isSorted == 'function') { - thisArg = callback; - callback = isSorted; - isSorted = false; - } - if (!callback) { - callback = identity; - } else if (thisArg) { - callback = iteratorBind(callback, thisArg); - } - while (++index < length) { - computed = callback(array[index], index, array); - if (isSorted - ? !index || seen[seen.length - 1] !== computed - : indexOf(seen, computed) < 0 - ) { - seen.push(computed); - result.push(array[index]); - } - } - return result; - } + var sortBy = createIterator(baseIteratorOptions, countByIteratorOptions, mapIteratorOptions, { + 'inLoop': { + 'array': + 'result[index] = {\n' + + ' criteria: callback(value, index, collection),\n' + + ' index: index,\n' + + ' value: value\n' + + '}', + 'object': + 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '({\n' + + ' criteria: callback(value, index, collection),\n' + + ' index: index,\n' + + ' value: value\n' + + '})' + }, + 'bottom': + 'result.sort(compareAscending);\n' + + 'length = result.length;\n' + + 'while (length--) {\n' + + ' result[length] = result[length].value\n' + + '}' + }); /** - * Produces a new array with all occurrences of the passed values removed using - * strict equality for comparisons, i.e. `===`. + * Converts the `collection`, to an array. Useful for converting the + * `arguments` object. * * @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. + * @category Collections + * @param {Array|Object|String} collection The collection to convert. + * @returns {Array} Returns the new converted array. * @example * - * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); + * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4); * // => [2, 3, 4] */ - function without(array) { - var result = []; - if (!array) { - return result; + function toArray(collection) { + if (!collection) { + return []; } - var index = -1, - length = array.length; - - while (++index < length) { - if (indexOf(arguments, array[index], 1) < 0) { - result.push(array[index]); - } + if (collection.toArray && isFunction(collection.toArray)) { + return collection.toArray(); } - return result; + var length = collection.length; + if (length > -1 && length === length >>> 0) { + return (noArraySliceOnStrings ? toString.call(collection) == stringClass : typeof collection == 'string') + ? collection.split('') + : slice.call(collection); + } + return values(collection); } /** - * Merges together the values of each of the arrays with the value at the - * corresponding position. 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. + * Examines each element in a `collection`, returning an array of all elements + * that contain the given `properties`. * * @static * @memberOf _ - * @category Arrays - * @param {Array} [array1, array2, ...] Arrays to process. - * @returns {Array} Returns a new array of merged arrays. + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Object} properties The object of properties/values to filter by. + * @returns {Array} Returns a new array of elements that contain the given `properties`. * @example * - * _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]); - * // => [['moe', 30, true], ['larry', 40, false], ['curly', 50, false]] + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 }, + * { 'name': 'curly', 'age': 60 } + * ]; + * + * _.where(stooges, { 'age': 40 }); + * // => [{ 'name': 'moe', 'age': 40 }] */ - function zip(array) { - if (!array) { - return []; - } - var index = -1, - length = max(pluck(arguments, 'length')), - result = Array(length); - - while (++index < length) { - result[index] = pluck(arguments, index); - } - return result; - } + var where = createIterator(filterIteratorOptions, { + 'args': 'collection, properties', + 'top': + 'var props = [];\n' + + 'forIn(properties, function(value, prop) { props.push(prop) });\n' + + 'var propsLength = props.length', + 'inLoop': + 'for (var prop, pass = true, propIndex = 0; propIndex < propsLength; propIndex++) {\n' + + ' prop = props[propIndex];\n' + + ' if (!(pass = value[prop] === properties[prop])) break\n' + + '}\n' + + 'pass && result.push(value)' + }); /*--------------------------------------------------------------------------*/ /** - * Creates a new function that is restricted to executing only after it is - * called `n` times. + * Creates a new array with all falsey values of `array` removed. The values + * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey. * * @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. + * @category Arrays + * @param {Array} array The array to compact. + * @returns {Array} Returns a new filtered array. * @example * - * var renderNotes = _.after(notes.length, render); - * _.forEach(notes, function(note) { - * note.asyncSave({ 'success': renderNotes }); - * }); - * // `renderNotes` is run once, after all notes have saved + * _.compact([0, 1, false, 2, '', 3]); + * // => [1, 2, 3] */ - function after(n, func) { - if (n < 1) { - return func(); + function compact(array) { + var result = []; + if (!array) { + return result; } - return function() { - if (--n < 1) { - return func.apply(this, arguments); + var index = -1, + length = array.length; + + while (++index < length) { + if (array[index]) { + result.push(array[index]); } - }; + } + return result; } - - /** - * Creates a new 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. Lazy defined methods may be bound by passing - * the object they are bound to as `func` and the method name as `thisArg`. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function|Object} func The function to bind or the object the method belongs to. - * @param {Mixed} [thisArg] The `this` binding of `func` or the method name. - * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. - * @returns {Function} Returns the new bound function. - * @example - * - * // basic bind - * var func = function(greeting) { - * return greeting + ': ' + this.name; - * }; - * - * func = _.bind(func, { 'name': 'moe' }, 'hi'); - * func(); - * // => 'hi: moe' - * - * // lazy bind - * var object = { - * 'name': 'moe', - * 'greet': function(greeting) { - * return greeting + ': ' + this.name; - * } - * }; - * - * var func = _.bind(object, 'greet', 'hi'); - * func(); - * // => 'hi: moe' + + /** + * Creates a new array of `array` elements not present in the other arrays + * using strict equality for comparisons, i.e. `===`. * - * object.greet = function(greeting) { - * return greeting + ', ' + this.name + '!'; - * }; + * @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 * - * func(); - * // => 'hi, moe!' + * _.difference([1, 2, 3, 4, 5], [5, 2, 10]); + * // => [1, 3, 4] */ - function bind(func, thisArg) { - var methodName, - isFunc = toString.call(func) == funcClass; - - // juggle arguments - if (!isFunc) { - methodName = thisArg; - thisArg = func; - } - // use if `Function#bind` is faster - else if (nativeBind) { - return nativeBind.call.apply(nativeBind, arguments); + function difference(array) { + var result = []; + if (!array) { + return result; } + var index = -1, + length = array.length, + flattened = concat.apply(result, arguments), + contains = cachedContains(flattened, length); - var partialArgs = slice.call(arguments, 2); - - function bound() { - // `Function#bind` spec - // http://es5.github.com/#x15.3.4.5 - var args = arguments, - thisBinding = thisArg; - - if (!isFunc) { - func = thisArg[methodName]; - } - if (partialArgs.length) { - args = args.length - ? concat.apply(partialArgs, args) - : partialArgs; - } - if (this instanceof bound) { - // get `func` instance if `bound` is invoked in a `new` expression - noop.prototype = func.prototype; - thisBinding = new noop; - - // mimic the constructor's `return` behavior - // http://es5.github.com/#x13.2.2 - var result = func.apply(thisBinding, args); - return objectTypes[typeof result] && result !== null - ? result - : thisBinding + while (++index < length) { + if (!contains(array[index])) { + result.push(array[index]); } - return func.apply(thisBinding, args); } - - return bound; + return result; } /** - * Binds methods on `object` to `object`, overwriting the existing method. - * If no method names are provided, all the function properties of `object` - * will be bound. + * Gets the first element of the `array`. Pass `n` to return the first `n` + * elements of the `array`. * * @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 the `object`. + * @alias head, take + * @category Arrays + * @param {Array} array The array to query. + * @param {Number} [n] The number of elements to return. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `n`. + * @returns {Mixed} Returns the first element or an array of the first `n` + * elements of `array`. * @example * - * var buttonView = { - * 'label': 'lodash', - * 'onClick': function() { alert('clicked: ' + this.label); } - * }; - * - * _.bindAll(buttonView); - * jQuery('#lodash_button').on('click', buttonView.onClick); - * // => When the button is clicked, `this.label` will have the correct value + * _.first([5, 4, 3, 2, 1]); + * // => 5 */ - function bindAll(object) { - var funcs = arguments, - index = 1; - - if (funcs.length == 1) { - index = 0; - funcs = functions(object); - } - for (var length = funcs.length; index < length; index++) { - object[funcs[index]] = bind(object[funcs[index]], object); + function first(array, n, guard) { + if (array) { + return (n == null || guard) ? array[0] : slice.call(array, 0, n); } - return object; } /** - * Creates a new function that is the composition of the passed functions, - * where each function consumes the return value of the function that follows. - * In math terms, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. + * Flattens a nested array (the nesting can be to any depth). If `shallow` is + * truthy, `array` will only be flattened a single level. * * @static * @memberOf _ - * @category Functions - * @param {Function} [func1, func2, ...] Functions to compose. - * @returns {Function} Returns the new composed function. + * @category Arrays + * @param {Array} array The array to compact. + * @param {Boolean} shallow A flag to indicate only flattening a single level. + * @returns {Array} Returns a new flattened array. * @example * - * var greet = function(name) { return 'hi: ' + name; }; - * var exclaim = function(statement) { return statement + '!'; }; - * var welcome = _.compose(exclaim, greet); - * welcome('moe'); - * // => 'hi: moe!' + * _.flatten([1, [2], [3, [[4]]]]); + * // => [1, 2, 3, 4]; + * + * _.flatten([1, [2], [3, [[4]]]], true); + * // => [1, 2, 3, [[4]]]; */ - function compose() { - var funcs = arguments; - return function() { - var args = arguments, - length = funcs.length; + function flatten(array, shallow) { + var result = []; + if (!array) { + return result; + } + var value, + index = -1, + length = array.length; - while (length--) { - args = [funcs[length].apply(this, args)]; + while (++index < length) { + value = array[index]; + if (isArray(value)) { + push.apply(result, shallow ? value : flatten(value)); + } else { + result.push(value); } - return args[0]; - }; + } + return result; } /** - * Creates a new function that will delay the execution of `func` until after - * `wait` milliseconds have elapsed since the last time it was invoked. Pass - * `true` for `immediate` to cause debounce to invoke `func` on the leading, - * instead of the trailing, edge of the `wait` timeout. Subsequent calls to - * the debounced function will return the result of the last `func` call. + * 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 `isSorted` will run a faster binary search. * * @static * @memberOf _ - * @category Functions - * @param {Function} func The function to debounce. - * @param {Number} wait The number of milliseconds to delay. - * @param {Boolean} immediate A flag to indicate execution is on the leading - * edge of the timeout. - * @returns {Function} Returns the new debounced function. + * @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 start searching from or + * `true` to perform a binary search on a sorted `array`. + * @returns {Number} Returns the index of the matched value or `-1`. * @example * - * var lazyLayout = _.debounce(calculateLayout, 300); - * jQuery(window).on('resize', lazyLayout); + * _.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 debounce(func, wait, immediate) { - var args, - result, - thisArg, - timeoutId; + function indexOf(array, value, fromIndex) { + if (!array) { + return -1; + } + var index = -1, + length = array.length; - function delayed() { - timeoutId = null; - if (!immediate) { - func.apply(thisArg, args); + if (fromIndex) { + if (typeof fromIndex == 'number') { + index = (fromIndex < 0 ? Math.max(0, length + fromIndex) : fromIndex) - 1; + } else { + index = sortedIndex(array, value); + return array[index] === value ? index : -1; } } - - return function() { - var isImmediate = immediate && !timeoutId; - args = arguments; - thisArg = this; - - clearTimeout(timeoutId); - timeoutId = setTimeout(delayed, wait); - - if (isImmediate) { - result = func.apply(thisArg, args); + while (++index < length) { + if (array[index] === value) { + return index; } - return result; - }; + } + return -1; } /** - * Executes the `func` function after `wait` milliseconds. Additional arguments - * are passed to `func` when it is invoked. + * Gets all but the last element of `array`. Pass `n` to exclude the last `n` + * elements from the result. * * @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 `setTimeout` timeout id. + * @category Arrays + * @param {Array} array The array to query. + * @param {Number} [n] The number of elements to return. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `n`. + * @returns {Array} Returns all but the last element or `n` elements of `array`. * @example * - * var log = _.bind(console.log, console); - * _.delay(log, 1000, 'logged later'); - * // => 'logged later' (Appears after one second.) + * _.initial([3, 2, 1]); + * // => [3, 2] */ - function delay(func, wait) { - var args = slice.call(arguments, 2); - return setTimeout(function() { return func.apply(undefined, args); }, wait); + function initial(array, n, guard) { + if (!array) { + return []; + } + return slice.call(array, 0, -((n == null || guard) ? 1 : n)); } /** - * Defers executing the `func` function until the current call stack has cleared. - * Additional arguments are passed to `func` when it is invoked. + * Computes the intersection of all the passed-in arrays using strict equality + * for comparisons, i.e. `===`. * * @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 `setTimeout` timeout id. + * @category Arrays + * @param {Array} [array1, array2, ...] Arrays to process. + * @returns {Array} Returns a new array of unique elements, in order, that are + * present in **all** of the arrays. * @example * - * _.defer(function() { alert('deferred'); }); - * // returns from the function before `alert` is called + * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); + * // => [1, 2] */ - function defer(func) { - var args = slice.call(arguments, 1); - return setTimeout(function() { return func.apply(undefined, args); }, 1); + function intersection(array) { + var result = []; + if (!array) { + return result; + } + var value, + argsLength = arguments.length, + cache = [], + index = -1, + length = array.length; + + array: while (++index < length) { + value = array[index]; + if (indexOf(result, value) < 0) { + for (var argsIndex = 1; argsIndex < argsLength; argsIndex++) { + if (!(cache[argsIndex] || (cache[argsIndex] = cachedContains(arguments[argsIndex])))(value)) { + continue array; + } + } + result.push(value); + } + } + return result; } /** - * Creates a new 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. + * Gets the last element of the `array`. Pass `n` to return the lasy `n` + * elementsvof the `array`. * * @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. + * @category Arrays + * @param {Array} array The array to query. + * @param {Number} [n] The number of elements to return. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `n`. + * @returns {Mixed} Returns the last element or an array of the last `n` + * elements of `array`. * @example * - * var fibonacci = _.memoize(function(n) { - * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); - * }); + * _.last([3, 2, 1]); + * // => 1 */ - function memoize(func, resolver) { - var cache = {}; - return function() { - var prop = resolver ? resolver.apply(this, arguments) : arguments[0]; - return hasOwnProperty.call(cache, prop) - ? cache[prop] - : (cache[prop] = func.apply(this, arguments)); - }; + function last(array, n, guard) { + if (array) { + var length = array.length; + return (n == null || guard) ? array[length - 1] : slice.call(array, -n || length); + } } /** - * Creates a new function that is restricted to one execution. Repeat calls to - * the function will return the value of the first call. + * Gets the index at which the last occurrence of `value` is found using + * strict equality for comparisons, i.e. `===`. * * @static * @memberOf _ - * @category Functions - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. + * @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 start searching from. + * @returns {Number} Returns the index of the matched value or `-1`. * @example * - * var initialize = _.once(createApplication); - * initialize(); - * initialize(); - * // Application is only created once. + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); + * // => 4 + * + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3); + * // => 1 */ - function once(func) { - var result, - ran = false; - - return function() { - if (ran) { - return result; + function lastIndexOf(array, value, fromIndex) { + if (!array) { + return -1; + } + var index = array.length; + if (fromIndex && typeof fromIndex == 'number') { + index = (fromIndex < 0 ? Math.max(0, index + fromIndex) : Math.min(fromIndex, index - 1)) + 1; + } + while (index--) { + if (array[index] === value) { + return index; } - ran = true; - result = func.apply(this, arguments); - return result; - }; + } + return -1; } /** - * Creates a new function that, when called, invokes `func` with any additional - * `partial` arguments prepended to those passed to the partially applied - * function. This method is similar `bind`, except it does **not** alter the - * `this` binding. + * 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 3 arguments; (value, index, array). * * @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. + * @category Arrays + * @param {Array} array The array to iterate over. + * @param {Function} [callback] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the maximum value. * @example * - * var greet = function(greeting, name) { return greeting + ': ' + name; }; - * var hi = _.partial(greet, 'hi'); - * hi('moe'); - * // => 'hi: moe' + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 }, + * { 'name': 'curly', 'age': 60 } + * ]; + * + * _.max(stooges, function(stooge) { return stooge.age; }); + * // => { 'name': 'curly', 'age': 60 }; */ - function partial(func) { - var args = slice.call(arguments, 1), - argsLength = args.length; + function max(array, callback, thisArg) { + var computed = -Infinity, + result = computed; - return function() { - var result, - others = arguments; + if (!array) { + return result; + } + var current, + index = -1, + length = array.length; - if (others.length) { - args.length = argsLength; - push.apply(args, others); + if (!callback) { + while (++index < length) { + if (array[index] > result) { + result = array[index]; + } } - result = args.length == 1 ? func.call(this, args[0]) : func.apply(this, args); - args.length = argsLength; return result; - }; + } + if (thisArg) { + callback = iteratorBind(callback, thisArg); + } + while (++index < length) { + current = callback(array[index], index, array); + if (current > computed) { + computed = current; + result = array[index]; + } + } + return result; } /** - * Creates a new function that, when executed, will only call the `func` - * function at most once per every `wait` milliseconds. If the throttled - * function is invoked more than once during the `wait` timeout, `func` will - * also be called on the trailing edge of the timeout. Subsequent calls to the - * throttled function will return the result of the last `func` call. + * 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 3 arguments; (value, index, array). * * @static * @memberOf _ - * @category Functions - * @param {Function} func The function to throttle. - * @param {Number} wait The number of milliseconds to throttle executions to. - * @returns {Function} Returns the new throttled function. + * @category Arrays + * @param {Array} array The array to iterate over. + * @param {Function} [callback] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the minimum value. * @example * - * var throttled = _.throttle(updatePosition, 100); - * jQuery(window).on('scroll', throttled); + * _.min([10, 5, 100, 2, 1000]); + * // => 2 */ - function throttle(func, wait) { - var args, - result, - thisArg, - timeoutId, - lastCalled = 0; + function min(array, callback, thisArg) { + var computed = Infinity, + result = computed; - function trailingCall() { - lastCalled = new Date; - timeoutId = null; - func.apply(thisArg, args); + if (!array) { + return result; } + var current, + index = -1, + length = array.length; - return function() { - var now = new Date, - remain = wait - (now - lastCalled); - - args = arguments; - thisArg = this; - - if (remain <= 0) { - lastCalled = now; - result = func.apply(thisArg, args); - } - else if (!timeoutId) { - timeoutId = setTimeout(trailingCall, remain); + if (!callback) { + while (++index < length) { + if (array[index] < result) { + result = array[index]; + } } return result; - }; + } + if (thisArg) { + callback = iteratorBind(callback, thisArg); + } + while (++index < length) { + current = callback(array[index], index, array); + if (current < computed) { + computed = current; + result = array[index]; + } + } + return result; } /** - * Create a new function that passes the `func` function to the `wrapper` - * function as its first argument. Additional arguments are appended to those - * passed to the `wrapper` function. + * Creates an array of numbers (positive and/or negative) progressing from + * `start` up to but not including `stop`. This method is a port of Python's + * `range()` function. See http://docs.python.org/library/functions.html#range. * * @static * @memberOf _ - * @category Functions - * @param {Function} func The function to wrap. - * @param {Function} wrapper The wrapper function. - * @param {Mixed} [arg1, arg2, ...] Arguments to append to those passed to the wrapper. - * @returns {Function} Returns the new function. + * @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 descrement by. + * @returns {Array} Returns a new range array. * @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(func, wrapper) { - return function() { - var args = [func]; - if (arguments.length) { - push.apply(args, arguments); - } - return wrapper.apply(this, args); - }; - } - - /*--------------------------------------------------------------------------*/ - - /** - * Create a shallow clone of the `value`. Any nested objects or arrays will be - * assigned by reference and not cloned. + * _.range(10); + * // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to clone. - * @returns {Mixed} Returns the cloned `value`. - * @example + * _.range(1, 11); + * // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] * - * _.clone({ 'name': 'moe' }); - * // => { 'name': 'moe' }; + * _.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 clone(value) { - return objectTypes[typeof value] && value !== null - ? (isArray(value) ? value.slice() : extend({}, value)) - : value; + 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://www.youtube.com/watch?v=XAqIpGU8ZZk#t=16m27s + var index = -1, + length = Math.max(0, Math.ceil((end - start) / step)), + result = Array(length); + + while (++index < length) { + result[index] = start; + start += step; + } + return result; } /** - * Assigns missing properties on `object` with default values from the defaults - * objects. Once a property is set, additional defaults of the same property - * will be ignored. + * The opposite of `_.initial`, this method gets all but the first value of + * `array`. Pass `n` to exclude the first `n` values from the result. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to populate. - * @param {Object} [defaults1, defaults2, ...] The defaults objects to apply to `object`. - * @returns {Object} Returns `object`. + * @alias tail + * @category Arrays + * @param {Array} array The array to query. + * @param {Number} [n] The number of elements to return. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `n`. + * @returns {Array} Returns all but the first value or `n` values of `array`. * @example * - * var iceCream = { 'flavor': 'chocolate' }; - * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'rainbow' }); - * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' } + * _.rest([3, 2, 1]); + * // => [2, 1] */ - var defaults = createIterator(extendIteratorOptions, { - 'inLoop': 'if (object[index] == null)' + extendIteratorOptions.inLoop - }); + function rest(array, n, guard) { + if (!array) { + return []; + } + return slice.call(array, (n == null || guard) ? 1 : n); + } /** - * Copies enumerable properties from the source objects to the `destination` object. - * Subsequent sources will overwrite propery assignments of previous sources. + * Creates a new 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 Objects - * @param {Object} object The destination object. - * @param {Object} [source1, source2, ...] The source objects. - * @returns {Object} Returns the destination object. + * @category Arrays + * @param {Array} array The array to shuffle. + * @returns {Array} Returns a new shuffled array. * @example * - * _.extend({ 'name': 'moe' }, { 'age': 40 }); - * // => { 'name': 'moe', 'age': 40 } + * _.shuffle([1, 2, 3, 4, 5, 6]); + * // => [4, 1, 6, 3, 5, 2] */ - var extend = createIterator(extendIteratorOptions); + function shuffle(array) { + if (!array) { + return []; + } + var rand, + index = -1, + length = array.length, + result = Array(length); + + while (++index < length) { + rand = Math.floor(Math.random() * (index + 1)); + result[index] = result[rand]; + result[rand] = array[index]; + } + 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 3 arguments; (value, key, object). + * 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 1 argument; (value). * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to iterate over. - * @param {Function} callback The function called per iteration. + * @category Arrays + * @param {Array} array The array to iterate over. + * @param {Mixed} value The value to evaluate. + * @param {Function} [callback=identity] The function called per iteration. * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Object} Returns the `object`. + * @returns {Number} Returns the index at which the value should be inserted + * into `array`. * @example * - * function Dog(name) { - * this.name = name; - * } + * _.sortedIndex([20, 30, 40], 35); + * // => 2 * - * Dog.prototype.bark = function() { - * alert('Woof, woof!'); + * var dict = { + * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'thirty-five': 35, 'fourty': 40 } * }; * - * _.forIn(new Dog('Dagny'), function(value, key) { - * alert(key); + * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { + * return dict.wordToNumber[word]; * }); - * // => alerts 'name' and 'bark' (order is not guaranteed) - */ - var forIn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions, { - 'useHas': false - }); - - /** - * Iterates over `object`'s own enumerable properties, executing the `callback` - * for each property. The `callback` is bound to `thisArg` and invoked with 3 - * arguments; (value, key, object). - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The object to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Object} Returns the `object`. - * @example + * // => 2 * - * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { - * alert(key); - * }); - * // => alerts '0', '1', and 'length' (order is not guaranteed) + * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { + * return this.wordToNumber[word]; + * }, dict); + * // => 2 */ - var forOwn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions); + function sortedIndex(array, value, callback, thisArg) { + if (!array) { + return 0; + } + var mid, + low = 0, + high = array.length; - /** - * Produces a sorted array of the 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', ...] - */ - var functions = createIterator({ - 'args': 'object', - 'init': '[]', - 'useHas': false, - 'inLoop': 'if (toString.call(object[index]) == funcClass) result.push(index)', - 'bottom': 'result.sort()' - }); + if (callback) { + if (thisArg) { + callback = bind(callback, thisArg); + } + value = callback(value); + while (low < high) { + mid = (low + high) >>> 1; + callback(array[mid]) < value ? low = mid + 1 : high = mid; + } + } else { + while (low < high) { + mid = (low + high) >>> 1; + array[mid] < value ? low = mid + 1 : high = mid; + } + } + return low; + } /** - * Checks if the specified object `property` exists and is a direct property, - * instead of an inherited property. + * Computes the union of the passed-in arrays using strict equality for + * comparisons, i.e. `===`. * * @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`. + * @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 * - * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); - * // => true + * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); + * // => [1, 2, 3, 101, 10] */ - function has(object, property) { - return hasOwnProperty.call(object, property); + function union() { + var index = -1, + result = [], + flattened = concat.apply(result, arguments), + length = flattened.length; + + while (++index < length) { + if (indexOf(result, flattened[index]) < 0) { + result.push(flattened[index]); + } + } + return result; } /** - * Checks if `value` is an `arguments` object. + * 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 a callback` before uniqueness is computed. + * The `callback` is bound to `thisArg` and invoked with 3 arguments; (value, index, array). * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is an `arguments` object, else `false`. + * @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} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Array} Returns a duplicate-value-free array. * @example * - * (function() { return _.isArguments(arguments); })(1, 2, 3); - * // => true + * _.uniq([1, 2, 1, 3, 1]); + * // => [1, 2, 3] * - * _.isArguments([1, 2, 3]); - * // => false + * _.uniq([1, 1, 2, 2, 3], true); + * // => [1, 2, 3] + * + * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); }); + * // => [1, 2, 3] + * + * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math); + * // => [1, 2, 3] */ - var isArguments = function(value) { - return toString.call(value) == '[object Arguments]'; - }; - // fallback for browser like IE < 9 which detect `arguments` as `[object Object]` - if (!isArguments(arguments)) { - isArguments = function(value) { - return !!(value && hasOwnProperty.call(value, 'callee')); - }; + function uniq(array, isSorted, callback, thisArg) { + var result = []; + if (!array) { + return result; + } + var computed, + index = -1, + length = array.length, + seen = []; + + // juggle arguments + if (typeof isSorted == 'function') { + thisArg = callback; + callback = isSorted; + isSorted = false; + } + if (!callback) { + callback = identity; + } else if (thisArg) { + callback = iteratorBind(callback, thisArg); + } + while (++index < length) { + computed = callback(array[index], index, array); + if (isSorted + ? !index || seen[seen.length - 1] !== computed + : indexOf(seen, computed) < 0 + ) { + seen.push(computed); + result.push(array[index]); + } + } + return result; } /** - * Checks if `value` is an array. + * Creates a new array with all occurrences of the passed values removed using + * strict equality for comparisons, i.e. `===`. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is an array, else `false`. + * @category Arrays + * @param {Array} array The array to filter. + * @param {Mixed} [value1, value2, ...] Values to remove. + * @returns {Array} Returns a new filtered array. * @example * - * (function() { return _.isArray(arguments); })(); - * // => false - * - * _.isArray([1, 2, 3]); - * // => true + * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); + * // => [2, 3, 4] */ - var isArray = nativeIsArray || function(value) { - return toString.call(value) == arrayClass; - }; + function without(array) { + var result = []; + if (!array) { + return result; + } + var index = -1, + length = array.length, + contains = cachedContains(arguments, 1, 20); + + while (++index < length) { + if (!contains(array[index])) { + result.push(array[index]); + } + } + return result; + } /** - * Checks if `value` is a boolean (`true` or `false`) value. + * 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 Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a boolean value, else `false`. + * @category Arrays + * @param {Array} [array1, array2, ...] Arrays to process. + * @returns {Array} Returns a new array of grouped elements. * @example * - * _.isBoolean(null); - * // => false + * _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]); + * // => [['moe', 30, true], ['larry', 40, false], ['curly', 50, false]] */ - function isBoolean(value) { - return value === true || value === false || toString.call(value) == boolClass; + function zip(array) { + if (!array) { + return []; + } + var index = -1, + length = max(pluck(arguments, 'length')), + result = Array(length); + + while (++index < length) { + result[index] = pluck(arguments, index); + } + return result; } /** - * Checks if `value` is a date. + * Creates an object composed from an array of `keys` and an array of `values`. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a date, else `false`. + * @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 * - * _.isDate(new Date); - * // => true + * _.zipObject(['moe', 'larry', 'curly'], [30, 40, 50]); + * // => { 'moe': 30, 'larry': 40, 'curly': 50 } */ - function isDate(value) { - return toString.call(value) == dateClass; + function zipObject(keys, values) { + if (!keys) { + return {}; + } + var index = -1, + length = keys.length, + result = {}; + + values || (values = []); + while (++index < length) { + result[keys[index]] = values[index]; + } + return result; } + /*--------------------------------------------------------------------------*/ + /** - * Checks if `value` is a DOM element. + * Creates a new function that is restricted to executing only after it is + * called `n` times. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a DOM element, else `false`. + * @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 * - * _.isElement(document.body); - * // => true + * var renderNotes = _.after(notes.length, render); + * _.forEach(notes, function(note) { + * note.asyncSave({ 'success': renderNotes }); + * }); + * // `renderNotes` is run once, after all notes have saved */ - function isElement(value) { - return !!(value && value.nodeType == 1); + function after(n, func) { + if (n < 1) { + return func(); + } + return function() { + if (--n < 1) { + return func.apply(this, arguments); + } + }; } /** - * Checks if `value` is empty. Arrays or strings with a length of `0` and - * objects with no own enumerable properties are considered "empty". + * Creates a new 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. Lazy defined methods may be bound by passing + * the object they are bound to as `func` and the method name as `thisArg`. * * @static * @memberOf _ - * @category Objects - * @param {Array|Object|String} value The value to inspect. - * @returns {Boolean} Returns `true` if the `value` is empty, else `false`. + * @category Functions + * @param {Function|Object} func The function to bind or the object the method belongs to. + * @param {Mixed} [thisArg] The `this` binding of `func` or the method name. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new bound function. * @example * - * _.isEmpty([1, 2, 3]); - * // => false + * // basic bind + * var func = function(greeting) { + * return greeting + ' ' + this.name; + * }; * - * _.isEmpty({}); - * // => true - */ - var isEmpty = createIterator({ - 'args': 'value', - 'init': 'true', - 'top': - 'var className = toString.call(value);\n' + - 'if (className == arrayClass || className == stringClass) return !value.length', - 'inLoop': { - 'object': 'return false' - } - }); - - /** - * Performs a deep comparison between two values to determine if they are - * equivalent to each other. + * func = _.bind(func, { 'name': 'moe' }, 'hi'); + * func(); + * // => 'hi moe' * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} a The value to compare. - * @param {Mixed} b The other value to compare. - * @param {Array} [stack] Internally used to keep track of "seen" objects to - * avoid circular references. - * @returns {Boolean} Returns `true` if the values are equvalent, else `false`. - * @example + * // lazy bind + * var object = { + * 'name': 'moe', + * 'greet': function(greeting) { + * return greeting + ' ' + this.name; + * } + * }; * - * var moe = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; - * var clone = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; + * var func = _.bind(object, 'greet', 'hi'); + * func(); + * // => 'hi moe' * - * moe == clone; - * // => false + * object.greet = function(greeting) { + * return greeting + ', ' + this.name + '!'; + * }; * - * _.isEqual(moe, clone); - * // => true + * func(); + * // => 'hi, moe!' */ - function isEqual(a, b, stack) { - stack || (stack = []); - - // exit early for identical values - if (a === b) { - // treat `+0` vs. `-0` as not equal - return a !== 0 || (1 / a == 1 / b); - } - // a strict comparison is necessary because `undefined == null` - if (a == null || b == null) { - return a === b; - } - // unwrap any wrapped objects - if (a._chain) { - a = a._wrapped; - } - if (b._chain) { - b = b._wrapped; - } - // invoke a custom `isEqual` method if one is provided - if (a.isEqual && toString.call(a.isEqual) == funcClass) { - return a.isEqual(b); - } - if (b.isEqual && toString.call(b.isEqual) == funcClass) { - return b.isEqual(a); - } - // compare [[Class]] names - var className = toString.call(a); - if (className != toString.call(b)) { - return false; - } - switch (className) { - // strings, numbers, dates, and booleans are compared by value - case stringClass: - // primitives and their corresponding object instances are equivalent; - // thus, `'5'` is quivalent to `new String('5')` - return a == String(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 boolClass: - case dateClass: - // coerce dates and booleans to numeric values, dates to milliseconds and booleans to 1 or 0; - // treat invalid dates coerced to `NaN` as not equal - return +a == +b; + function bind(func, thisArg) { + var methodName, + isFunc = isFunction(func); - // regexps are compared by their source and flags - case regexpClass: - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; - } - if (typeof a != 'object' || typeof b != 'object') { - return false; + // juggle arguments + if (!isFunc) { + methodName = thisArg; + thisArg = func; } - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = stack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (stack[length] == a) { - return true; - } + // use `Function#bind` if it exists and is fast + // (in V8 `Function#bind` is slower except when partially applied) + else if (isBindFast || (nativeBind && arguments.length > 2)) { + return nativeBind.call.apply(nativeBind, arguments); } - var index = -1, - result = true, - size = 0; - - // add the first collection to the stack of traversed objects - stack.push(a); + var partialArgs = slice.call(arguments, 2); - // recursively compare objects and arrays - if (className == arrayClass) { - // compare array lengths to determine if a deep comparison is necessary - size = a.length; - result = size == b.length; + function bound() { + // `Function#bind` spec + // http://es5.github.com/#x15.3.4.5 + var args = arguments, + thisBinding = thisArg; - if (result) { - // deep compare the contents, ignoring non-numeric properties - while (size--) { - if (!(result = isEqual(a[size], b[size], stack))) { - break; - } - } - } - } else { - // objects with different constructors are not equivalent - if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) { - return false; - } - // deep compare objects. - for (var prop in a) { - if (hasOwnProperty.call(a, prop)) { - // count the number of properties. - size++; - // deep compare each property value. - if (!(result = hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack))) { - break; - } - } + if (!isFunc) { + func = thisArg[methodName]; } - // ensure both objects have the same number of properties - if (result) { - for (prop in b) { - // Adobe's JS engine, embedded in applications like InDesign, has a - // bug that causes `!size--` to throw an error so it must be wrapped - // in parentheses. - // https://github.com/documentcloud/underscore/issues/355 - if (hasOwnProperty.call(b, prop) && !(size--)) { - break; - } - } - result = !size; - } - // handle JScript [[DontEnum]] bug - if (result && hasDontEnumBug) { - while (++index < 7) { - prop = shadowed[index]; - if (hasOwnProperty.call(a, prop)) { - if (!(result = hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack))) { - break; - } - } - } + if (partialArgs.length) { + args = args.length + ? partialArgs.concat(slice.call(args)) + : partialArgs; + } + if (this instanceof bound) { + // get `func` instance if `bound` is invoked in a `new` expression + noop.prototype = func.prototype; + thisBinding = new noop; + + // mimic the constructor's `return` behavior + // http://es5.github.com/#x13.2.2 + var result = func.apply(thisBinding, args); + return result && objectTypes[typeof result] + ? result + : thisBinding } + return func.apply(thisBinding, args); } - // remove the first collection from the stack of traversed objects - stack.pop(); - return result; + return bound; } /** - * Checks if `value` is a finite number. - * Note: This is not the same as native `isFinite`, which will return true for - * booleans and other values. See http://es5.github.com/#x15.1.2.5. + * Binds methods on `object` to `object`, overwriting the existing method. + * If no method names are provided, all the function properties of `object` + * will be bound. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a finite number, else `false`. + * @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 * - * _.isFinite(-101); - * // => true - * - * _.isFinite('10'); - * // => false + * var buttonView = { + * 'label': 'lodash', + * 'onClick': function() { alert('clicked: ' + this.label); } + * }; * - * _.isFinite(Infinity); - * // => false + * _.bindAll(buttonView); + * jQuery('#lodash_button').on('click', buttonView.onClick); + * // => When the button is clicked, `this.label` will have the correct value */ - function isFinite(value) { - return nativeIsFinite(value) && toString.call(value) == numberClass; - } + var bindAll = createIterator({ + 'useHas': false, + 'useStrict': false, + 'args': 'object', + 'init': 'object', + 'top': + 'var funcs = arguments,\n' + + ' length = funcs.length;\n' + + 'if (length > 1) {\n' + + ' for (var index = 1; index < length; index++) {\n' + + ' result[funcs[index]] = bind(result[funcs[index]], result)\n' + + ' }\n' + + ' return result\n' + + '}', + 'inLoop': + 'if (isFunction(result[index])) {\n' + + ' result[index] = bind(result[index], result)\n' + + '}' + }); /** - * Checks if `value` is a function. + * Creates a new function that is the composition of the passed functions, + * where each function consumes the return value of the function that follows. + * In math terms, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a function, else `false`. + * @category Functions + * @param {Function} [func1, func2, ...] Functions to compose. + * @returns {Function} Returns the new composed function. * @example * - * _.isFunction(''.concat); - * // => true + * var greet = function(name) { return 'hi: ' + name; }; + * var exclaim = function(statement) { return statement + '!'; }; + * var welcome = _.compose(exclaim, greet); + * welcome('moe'); + * // => 'hi: moe!' */ - function isFunction(value) { - return toString.call(value) == funcClass; - } + function compose() { + var funcs = arguments; + return function() { + var args = arguments, + length = funcs.length; - /** - * Checks if `value` is the language type of Object. - * (e.g. arrays, functions, objects, regexps, `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); - * // => false - */ - function isObject(value) { - // check if the value is the ECMAScript language type of Object - // http://es5.github.com/#x8 - return objectTypes[typeof value] && value !== null; + while (length--) { + args = [funcs[length].apply(this, args)]; + } + return args[0]; + }; } /** - * 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. + * Creates a new function that will delay the execution of `func` until after + * `wait` milliseconds have elapsed since the last time it was invoked. Pass + * `true` for `immediate` to cause debounce to invoke `func` on the leading, + * instead of the trailing, edge of the `wait` timeout. Subsequent calls to + * the debounced function will return the result of the last `func` call. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is `NaN`, else `false`. + * @category Functions + * @param {Function} func The function to debounce. + * @param {Number} wait The number of milliseconds to delay. + * @param {Boolean} immediate A flag to indicate execution is on the leading + * edge of the timeout. + * @returns {Function} Returns the new debounced function. * @example * - * _.isNaN(NaN); - * // => true - * - * _.isNaN(new Number(NaN)); - * // => true - * - * isNaN(undefined); - * // => true - * - * _.isNaN(undefined); - * // => false + * var lazyLayout = _.debounce(calculateLayout, 300); + * jQuery(window).on('resize', lazyLayout); */ - 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 toString.call(value) == numberClass && value != +value - } + function debounce(func, wait, immediate) { + var args, + result, + thisArg, + timeoutId; - /** - * Checks if `value` is `null`. - * - * @deprecated - * @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; - } + function delayed() { + timeoutId = null; + if (!immediate) { + func.apply(thisArg, args); + } + } - /** - * 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 toString.call(value) == numberClass; + return function() { + var isImmediate = immediate && !timeoutId; + args = arguments; + thisArg = this; + + clearTimeout(timeoutId); + timeoutId = setTimeout(delayed, wait); + + if (isImmediate) { + result = func.apply(thisArg, args); + } + return result; + }; } /** - * Checks if `value` is a regular expression. + * Executes the `func` function after `wait` milliseconds. Additional arguments + * will be passed to `func` when it is invoked. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a regular expression, else `false`. + * @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 `setTimeout` timeout id. * @example * - * _.isRegExp(/moe/); - * // => true + * var log = _.bind(console.log, console); + * _.delay(log, 1000, 'logged later'); + * // => 'logged later' (Appears after one second.) */ - function isRegExp(value) { - return toString.call(value) == regexpClass; + function delay(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function() { return func.apply(undefined, args); }, wait); } /** - * Checks if `value` is a string. + * 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 Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a string, else `false`. + * @category Functions + * @param {Function} func The function to defer. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. + * @returns {Number} Returns the `setTimeout` timeout id. * @example * - * _.isString('moe'); - * // => true + * _.defer(function() { alert('deferred'); }); + * // returns from the function before `alert` is called */ - function isString(value) { - return toString.call(value) == stringClass; + function defer(func) { + var args = slice.call(arguments, 1); + return setTimeout(function() { return func.apply(undefined, args); }, 1); } /** - * Checks if `value` is `undefined`. + * Creates a new 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. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is `undefined`, else `false`. + * @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 * - * _.isUndefined(void 0); - * // => true - */ - function isUndefined(value) { - return value === undefined; + * var fibonacci = _.memoize(function(n) { + * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); + * }); + */ + function memoize(func, resolver) { + var cache = {}; + return function() { + var prop = resolver ? resolver.apply(this, arguments) : arguments[0]; + return hasOwnProperty.call(cache, prop) + ? cache[prop] + : (cache[prop] = func.apply(this, arguments)); + }; } /** - * Produces an array of object`'s own enumerable property names. + * Creates a new function that is restricted to one execution. Repeat calls to + * the function will return the value of the first call. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property names. + * @category Functions + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. * @example * - * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); - * // => ['one', 'two', 'three'] (order is not guaranteed) + * var initialize = _.once(createApplication); + * initialize(); + * initialize(); + * // Application is only created once. */ - var keys = !nativeKeys ? shimKeys : function(object) { - // avoid iterating over the `prototype` property - return typeof object == 'function' - ? shimKeys(object) - : nativeKeys(object); - }; + function once(func) { + var result, + ran = false; + + 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 an object composed of the specified properties. Property names may - * be specified as individual arguments or as arrays of property names. + * Creates a new function that, when called, invokes `func` with any additional + * `partial` arguments prepended to those passed to the new function. This method + * is similar `bind`, except it does **not** alter the `this` binding. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to pluck. - * @param {Object} [prop1, prop2, ...] The properties to pick. - * @returns {Object} Returns an object composed of the picked properties. + * @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 * - * _.pick({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'name', 'age'); - * // => { 'name': 'moe', 'age': 40 } + * var greet = function(greeting, name) { return greeting + ': ' + name; }; + * var hi = _.partial(greet, 'hi'); + * hi('moe'); + * // => 'hi: moe' */ - function pick(object) { - var prop, - index = 0, - props = concat.apply(ArrayProto, arguments), - length = props.length, - result = {}; + function partial(func) { + var args = slice.call(arguments, 1), + argsLength = args.length; - // start `index` at `1` to skip `object` - while (++index < length) { - prop = props[index]; - if (prop in object) { - result[prop] = object[prop]; + return function() { + var result, + others = arguments; + + if (others.length) { + args.length = argsLength; + push.apply(args, others); } - } - return result; + result = args.length == 1 ? func.call(this, args[0]) : func.apply(this, args); + args.length = argsLength; + return result; + }; } /** - * Gets the size of `value` by returning `value.length` if `value` is a string - * or array, or the number of own enumerable properties if `value` is an object. + * Creates a new function that, when executed, will only call the `func` + * function at most once per every `wait` milliseconds. If the throttled + * function is invoked more than once during the `wait` timeout, `func` will + * also be called on the trailing edge of the timeout. Subsequent calls to the + * throttled function will return the result of the last `func` call. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Array|Object|String} value The value to inspect. - * @returns {Number} Returns `value.length` if `value` is a string or array, - * or the number of own enumerable properties if `value` is an object. + * @category Functions + * @param {Function} func The function to throttle. + * @param {Number} wait The number of milliseconds to throttle executions to. + * @returns {Function} Returns the new throttled function. * @example * - * _.size([1, 2]); - * // => 2 - * - * _.size({ 'one': 1, 'two': 2, 'three': 3 }); - * // => 3 - * - * _.size('curly'); - * // => 5 + * var throttled = _.throttle(updatePosition, 100); + * jQuery(window).on('scroll', throttled); */ - function size(value) { - if (!value) { - return 0; + function throttle(func, wait) { + var args, + result, + thisArg, + timeoutId, + lastCalled = 0; + + function trailingCall() { + lastCalled = new Date; + timeoutId = null; + func.apply(thisArg, args); } - var length = value.length; - return length === length >>> 0 ? value.length : keys(value).length; + + return function() { + var now = new Date, + remain = wait - (now - lastCalled); + + args = arguments; + thisArg = this; + + if (remain <= 0) { + lastCalled = now; + result = func.apply(thisArg, args); + } + else if (!timeoutId) { + timeoutId = setTimeout(trailingCall, remain); + } + return result; + }; } /** - * Produces an array of `object`'s own enumerable property values. + * Creates a new function that passes `value` to the `wrapper` function as its + * first argument. Additional arguments passed to the new function are appended + * to those passed to the `wrapper` function. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property values. + * @category Functions + * @param {Mixed} value The value to wrap. + * @param {Function} wrapper The wrapper function. + * @returns {Function} Returns the new function. * @example * - * _.values({ 'one': 1, 'two': 2, 'three': 3 }); - * // => [1, 2, 3] + * var hello = function(name) { return 'hello: ' + name; }; + * hello = _.wrap(hello, function(func) { + * return 'before, ' + func('moe') + ', after'; + * }); + * hello(); + * // => 'before, hello: moe, after' */ - var values = createIterator({ - 'args': 'object', - 'init': '[]', - 'inLoop': 'result.push(object[index])' - }); + function wrap(value, wrapper) { + return function() { + var args = [value]; + if (arguments.length) { + push.apply(args, arguments); + } + return wrapper.apply(this, args); + }; + } /*--------------------------------------------------------------------------*/ /** - * Escapes a string for inclusion in HTML, replacing `&`, `<`, `"`, and `'` - * characters. + * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their + * corresponding HTML entities. * * @static * @memberOf _ @@ -3919,8 +4806,8 @@ * @returns {String} Returns the escaped string. * @example * - * _.escape('Curly, Larry & Moe'); - * // => "Curly, Larry & Moe" + * _.escape('Moe, Larry & Curly'); + * // => "Moe, Larry & Curly" */ function escape(string) { return string == null ? '' : (string + '').replace(reUnescapedHtml, escapeHtmlChar); @@ -3928,6 +4815,7 @@ /** * This function returns the first argument passed to it. + * * Note: It is used throughout Lo-Dash as a default callback. * * @static @@ -3961,11 +4849,11 @@ * } * }); * - * _.capitalize('curly'); - * // => 'Curly' - * - * _('larry').capitalize(); + * _.capitalize('larry'); * // => 'Larry' + * + * _('curly').capitalize(); + * // => 'Curly' */ function mixin(object) { forEach(functions(object), function(methodName) { @@ -4037,13 +4925,20 @@ return null; } var value = object[property]; - return toString.call(value) == funcClass ? object[property]() : value; + 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 + * + * Note: Lo-Dash may be used in Chrome extensions by either creating a `lodash csp` + * build and avoiding `_.template` use, or loading Lo-Dash in a sandboxed page. + * See http://developer.chrome.com/trunk/extensions/sandboxingEval.html + * * @static * @memberOf _ * @category Utilities @@ -4054,41 +4949,47 @@ * is given, else it returns the interpolated text. * @example * - * // using compiled template + * // 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', 'curly', 'larry'] }); - * // => '
  • moe
  • curly
  • larry
  • ' + * _.template(list, { 'people': ['moe', 'larry', 'curly'] }); + * // => '
  • moe
  • larry
  • curly
  • ' * - * var template = _.template('<%- value %>'); - * template({ 'value': ' + * // using the `source` property to inline compiled templates for meaningful + * // line numbers in error messages and a stack trace + * fs.writeFileSync(path.join(cwd, 'jst.js'), '\ + * var JST = {\ + * "main": ' + _.template(mainText).source + '\ + * };\ + * '); */ function template(text, data, options) { // based on John Resig's `tmpl` implementation @@ -4096,25 +4997,28 @@ // and Laura Doktorova's doT.js // https://github.com/olado/doT options || (options = {}); + text += ''; var isEvaluating, - isInterpolating, result, - defaults = lodash.templateSettings, escapeDelimiter = options.escape, evaluateDelimiter = options.evaluate, interpolateDelimiter = options.interpolate, - variable = options.variable; + settings = lodash.templateSettings, + variable = options.variable || settings.variable, + hasVariable = variable; - // use template defaults if no option is provided + // use default settings if no options object is provided if (escapeDelimiter == null) { - escapeDelimiter = defaults.escape; + escapeDelimiter = settings.escape; } if (evaluateDelimiter == null) { - evaluateDelimiter = defaults.evaluate; + // use `false` as the fallback value, instead of leaving it `undefined`, + // so the initial assignment of `reEvaluateDelimiter` will still occur + evaluateDelimiter = settings.evaluate || false; } if (interpolateDelimiter == null) { - interpolateDelimiter = defaults.interpolate; + interpolateDelimiter = settings.interpolate; } // tokenize delimiters to avoid escaping them @@ -4122,37 +5026,66 @@ text = text.replace(escapeDelimiter, tokenizeEscape); } if (interpolateDelimiter) { - isInterpolating = text != (text = text.replace(interpolateDelimiter, tokenizeInterpolate)); + text = text.replace(interpolateDelimiter, tokenizeInterpolate); } - if (evaluateDelimiter) { - isEvaluating = text != (text = text.replace(evaluateDelimiter, tokenizeEvaluate)); + if (evaluateDelimiter != lastEvaluateDelimiter) { + // generate `reEvaluateDelimiter` to match `_.templateSettings.evaluate` + // and internal ``, `` delimiters + lastEvaluateDelimiter = evaluateDelimiter; + reEvaluateDelimiter = RegExp( + '|' + + (evaluateDelimiter ? '|' + evaluateDelimiter.source : '') + , 'g'); } + isEvaluating = tokenized.length; + text = text.replace(reEvaluateDelimiter, tokenizeEvaluate); + isEvaluating = isEvaluating != tokenized.length; // escape characters that cannot be included in string literals and // detokenize delimiter code snippets - text = "__p='" + text + text = "__p += '" + text .replace(reUnescapedString, escapeStringChar) .replace(reToken, detokenize) + "';\n"; // clear stored code snippets tokenized.length = 0; - // if `options.variable` is not specified, add `data` to the top of the scope chain - if (!variable) { - variable = defaults.variable; - text = 'with (' + variable + ' || {}) {\n' + text + '\n}\n'; + // if `variable` is not specified and the template contains "evaluate" + // delimiters, wrap a with-statement around the generated code to add the + // data object to the top of the scope chain + if (!hasVariable) { + variable = lastVariable || 'obj'; + + if (isEvaluating) { + text = 'with (' + variable + ') {\n' + text + '\n}\n'; + } + else { + if (variable != lastVariable) { + // generate `reDoubleVariable` to match references like `obj.obj` inside + // transformed "escape" and "interpolate" delimiters + lastVariable = variable; + reDoubleVariable = RegExp('(\\(\\s*)' + variable + '\\.' + variable + '\\b', 'g'); + } + // avoid a with-statement by prepending data object references to property names + text = text + .replace(reInsertVariable, '$&' + variable + '.') + .replace(reDoubleVariable, '$1__d'); + } } + // cleanup code by stripping empty strings + text = ( isEvaluating ? text.replace(reEmptyStringLeading, '') : text) + .replace(reEmptyStringMiddle, '$1') + .replace(reEmptyStringTrailing, '$1;'); + + // frame code as the function body text = 'function(' + variable + ') {\n' + - 'var __p' + - (isInterpolating - ? ', __t' - : '' - ) + + (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') + + 'var __t, __p = \'\', __e = _.escape' + (isEvaluating ? ', __j = Array.prototype.join;\n' + 'function print() { __p += __j.call(arguments, \'\') }\n' - : ';\n' + : (hasVariable ? '' : ', __d = ' + variable + '.' + variable + ' || ' + variable) + ';\n' ) + text + 'return __p\n}'; @@ -4163,14 +5096,21 @@ text += '\n//@ sourceURL=/lodash/template/source[' + (templateCounter++) + ']'; } - result = Function('_', 'return ' + text)(lodash); + try { + result = Function('_', 'return ' + text)(lodash); + } catch(e) { + // defer syntax errors until the compiled template is executed to allow + // examining the `source` property beforehand and for consistency, + // because other template related errors occur at execution + result = function() { throw e; }; + } if (data) { return result(data); } - // provide the compiled function's source via its `toString()` method, in + // provide the compiled function's source via its `toString` method, in // supported environments, or the `source` property as a convenience for - // build time precompilation + // inlining compiled templates during the build process result.source = text; return result; } @@ -4206,6 +5146,24 @@ } } + /** + * Converts the HTML entities `&`, `<`, `>`, `"`, and `'` + * in `string` to their corresponding characters. + * + * @static + * @memberOf _ + * @category Utilities + * @param {String} string The string to unescape. + * @returns {String} Returns the unescaped string. + * @example + * + * _.unescape('Moe, Larry & Curly'); + * // => "Moe, Larry & Curly" + */ + function unescape(string) { + return string == null ? '' : (string + '').replace(reEscapedHtml, unescapeHtmlChar); + } + /** * Generates a unique id. If `prefix` is passed, the id will be appended to it. * @@ -4264,7 +5222,7 @@ * @static * @memberOf _ * @category Chaining - * @param {Mixed} value The value to pass to `callback`. + * @param {Mixed} value The value to pass to `interceptor`. * @param {Function} interceptor The function to invoke. * @returns {Mixed} Returns `value`. * @example @@ -4325,7 +5283,7 @@ * @memberOf _ * @type String */ - lodash.VERSION = '0.3.2'; + lodash.VERSION = '0.6.1'; // assign static methods lodash.after = after; @@ -4336,11 +5294,13 @@ lodash.compact = compact; lodash.compose = compose; lodash.contains = contains; + lodash.countBy = countBy; lodash.debounce = debounce; lodash.defaults = defaults; lodash.defer = defer; lodash.delay = delay; lodash.difference = difference; + lodash.drop = drop; lodash.escape = escape; lodash.every = every; lodash.extend = extend; @@ -4381,6 +5341,7 @@ lodash.map = map; lodash.max = max; lodash.memoize = memoize; + lodash.merge = merge; lodash.min = min; lodash.mixin = mixin; lodash.noConflict = noConflict; @@ -4404,13 +5365,16 @@ lodash.throttle = throttle; lodash.times = times; lodash.toArray = toArray; + lodash.unescape = unescape; lodash.union = union; lodash.uniq = uniq; lodash.uniqueId = uniqueId; lodash.values = values; + lodash.where = where; lodash.without = without; lodash.wrap = wrap; lodash.zip = zip; + lodash.zipObject = zipObject; // assign aliases lodash.all = every; @@ -4424,6 +5388,7 @@ lodash.include = contains; lodash.inject = reduce; lodash.methods = functions; + lodash.omit = drop; lodash.select = filter; lodash.tail = rest; lodash.take = first; @@ -4454,12 +5419,9 @@ var value = this._wrapped; func.apply(value, arguments); - // IE compatibility mode and IE < 9 have buggy Array `shift()` and `splice()` - // functions that fail to remove the last element, `value[0]`, of - // array-like objects even though the `length` property is set to `0`. - // The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` - // is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. - if (value.length === 0) { + // avoid array-like object bugs with `Array#shift` and `Array#splice` in + // Firefox < 10 and IE < 9 + if (hasObjectSpliceBug && value.length === 0) { delete value[0]; } if (this._chain) { @@ -5051,7 +6013,7 @@ })(this); /** -* Miso.Dataset - v0.2.1 - 6/29/2012 +* Miso.Dataset - v0.2.2 - 9/3/2012 * http://github.com/misoproject/dataset * Copyright (c) 2012 Alex Graul, Irene Ros; * Dual Licensed: MIT, GPL @@ -5061,8 +6023,7 @@ (function(global, _) { - /* @exports namespace */ - var Miso = global.Miso = {}; + var Miso = global.Miso || (global.Miso = {}); Miso.typeOf = function(value, options) { var types = _.keys(Miso.types), @@ -5086,29 +6047,37 @@ mixed : { name : 'mixed', coerce : function(v) { + if (_.isNull(v) || typeof v === "undefined" || _.isNaN(v)) { + return null; + } return v; }, test : function(v) { return true; }, compare : function(s1, s2) { - if (s1 < s2) { return -1; } - if (s1 > s2) { return 1; } - return 0; + if ( _.isEqual(s1, s2) ) { return 0; } + if (s1 < s2) { return -1;} + if (s1 > s2) { return 1; } }, numeric : function(v) { - return _.isNaN( Number(v) ) ? null : Number(v); + return v === null || _.isNaN(+v) ? null : +v; } }, string : { name : "string", coerce : function(v) { - return v == null ? null : v.toString(); + if (_.isNaN(v) || v === null || typeof v === "undefined") { + return null; + } + return v.toString(); }, + test : function(v) { return (v === null || typeof v === "undefined" || typeof v === 'string'); }, + compare : function(s1, s2) { if (s1 == null && s2 != null) { return -1; } if (s1 != null && s2 == null) { return 1; } @@ -5132,6 +6101,9 @@ name : "boolean", regexp : /^(true|false)$/, coerce : function(v) { + if (_.isNaN(v) || v === null || typeof v === "undefined") { + return null; + } if (v === 'false') { return false; } return Boolean(v); }, @@ -5150,7 +6122,7 @@ return (n1 < n2 ? -1 : 1); }, numeric : function(value) { - if (_.isNaN(value)) { + if (value === null || _.isNaN(value)) { return null; } else { return (value) ? 1 : 0; @@ -5160,12 +6132,13 @@ number : { name : "number", - regexp : /^[\-\.]?[0-9]+([\.][0-9]+)?$/, + regexp : /^\s*[\-\.]?[0-9]+([\.][0-9]+)?\s*$/, coerce : function(v) { - if (_.isNull(v)) { + var cv = +v; + if (_.isNull(v) || typeof v === "undefined" || _.isNaN(cv)) { return null; } - return _.isNaN(v) ? null : +v; + return cv; }, test : function(v) { if (v === null || typeof v === "undefined" || typeof v === 'number' || this.regexp.test( v ) ) { @@ -5182,6 +6155,9 @@ return (n1 < n2 ? -1 : 1); }, numeric : function(value) { + if (_.isNaN(value) || value === null) { + return null; + } return value; } }, @@ -5231,6 +6207,11 @@ coerce : function(v, options) { options = options || {}; + + if (_.isNull(v) || typeof v === "undefined" || _.isNaN(v)) { + return null; + } + // if string, then parse as a time if (_.isString(v)) { var format = options.format || this.format; @@ -5263,6 +6244,9 @@ return 0; }, numeric : function( value ) { + if (_.isNaN(value) || value === null) { + return null; + } return value.valueOf(); } } @@ -5617,6 +6601,35 @@ }, this); }, + /** + * If this is a computed column, it calculates the value + * for this column and adds it to the data. + * Parameters: + * row - the row from which column is computed. + * i - Optional. the index at which this value will get added. + * Returns + * val - the computed value + */ + compute : function(row, i) { + if (this.func) { + var val = this.func(row); + if (typeof i !== "undefined") { + this.data[i] = val; + } else { + this.data.push(val); + } + + return val; + } + }, + + /** + * returns true if this is a computed column. False otherwise. + */ + isComputed : function() { + return !_.isUndefined(this.func); + }, + _sum : function() { return _.sum(this.data); }, @@ -6046,6 +7059,11 @@ _.each(row, function(value, key) { var column = this.column(key); + // is this a computed column? if so throw an error + if (column.isComputed()) { + throw "You're trying to update a computed column. Those get computed!"; + } + // if we suddenly see values for data that didn't exist before as a column // just drop it. First fetch defines the column structure. if (typeof column !== "undefined") { @@ -6065,18 +7083,28 @@ } else { throw("incorrect value '" + row[column.name] + "' of type " + Miso.typeOf(row[column.name], column) + - " passed to column with type " + column.type); + " passed to column '" + column.name + "' with type " + column.type); } } }, this); + // do we have any computed columns? If so we need to calculate their values. + if (this._computedColumns) { + _.each(this._computedColumns, function(column) { + var newVal = column.compute(row); + row[column.name] = newVal; + }); + } + // if we don't have a comparator, just append them at the end. if (_.isUndefined(this.comparator)) { // add all data _.each(this._columns, function(column) { - column.data.push(!_.isUndefined(row[column.name]) && !_.isNull(row[column.name]) ? row[column.name] : null); + if (!column.isComputed()) { + column.data.push(!_.isUndefined(row[column.name]) && !_.isNull(row[column.name]) ? row[column.name] : null); + } }); this.length++; @@ -6523,6 +7551,7 @@ Version 0.0.1.2 this._columns = []; this._columnPositionByName = {}; + this._computedColumns = []; if (typeof options !== "undefined") { options = options || {}; @@ -6615,8 +7644,6 @@ Version 0.0.1.2 this.uniqueAgainst = options.uniqueAgainst; } - - // if there is no data and no url set, we must be building // the dataset from scratch, so create an id column. if (_.isUndefined(options.data) && _.isUndefined(options.url)) { @@ -6723,43 +7750,33 @@ Version 0.0.1.2 againstColumn : function(data) { var rows = [], - colNames = _.keys(data), row, - // get against unique col - uniqCol = this.column(this.uniqueAgainst), - len = data[this._columns[1].name].length, - dataLength = _.max(_.map(colNames, function(name) { - return data[name].length; - }, this)); - - var posToRemove = [], i; - for(i = 0; i < len; i++) { - - var datum = data[this.uniqueAgainst][i]; - // this is a non unique row, remove it from all the data - // arrays - if (uniqCol.data.indexOf(datum) !== -1) { - posToRemove.push(i); - } - } - - // sort and reverse the removal ids, this way we won't - // lose position by removing an early id that will shift - // array and throw all other ids off. - posToRemove.sort().reverse(); + uniqName = this.uniqueAgainst, + uniqCol = this.column(uniqName), + toAdd = [], + toUpdate = [], + toRemove = []; + + _.each(data[uniqName], function(key, dataIndex) { + var rowIndex = uniqCol.data.indexOf( Miso.types[uniqCol.type].coerce(key) ); + + var row = {}; + _.each(data, function(col, name) { + row[name] = col[dataIndex]; + }); - for(i = 0; i < dataLength; i++) { - if (posToRemove.indexOf(i) === -1) { - row = {}; - for(var j = 0; j < colNames.length; j++) { - row[colNames[j]] = data[colNames[j]][i]; - } - rows.push(row); + if (rowIndex === -1) { + toAdd.push( row ); + } else { + toUpdate.push( row ); + var oldRow = this.rowById(this.column('_id').data[rowIndex])._id; + this.update(oldRow, row); } + }, this); + if (toAdd.length > 0) { + this.add(toAdd); } - - this.add(rows); }, //Always blindly add new rows @@ -6842,6 +7859,46 @@ Version 0.0.1.2 }, this); }, + /** + * Allows adding of a computed column. A computed column is + * a column that is somehow based on the other existing columns. + * Parameters: + * name : name of new column + * type : The type of the column based on existing types. + * func : The way that the column is derived. It takes a row as a parameter. + */ + addComputedColumn : function(name, type, func) { + // check if we already ahve a column by this name. + if ( !_.isUndefined(this.column(name)) ) { + throw "There is already a column by this name."; + } else { + + // check that this is a known type. + if (typeof Miso.types[type] === "undefined") { + throw "The type " + type + " doesn't exist"; + } + + var column = new Miso.Column({ + name : name, + type : type, + func : _.bind(func, this) + }); + + this._columns.push(column); + this._computedColumns.push(column); + this._columnPositionByName[column.name] = this._columns.length - 1; + + // do we already have data? if so compute the values for this column. + if (this.length > 0) { + this.each(function(row, i) { + column.compute(row, i); + }, this); + } + + return column; + } + }, + /** * Adds a single column to the dataset * Parameters: @@ -7005,8 +8062,15 @@ Version 0.0.1.2 newKeys = _.keys(props); _.each(newKeys, function(columnName) { + c = this.column(columnName); + // check if we're trying to update a computed column. If so + // fail. + if (c.isComputed()) { + throw "You're trying to update a computed column. Those get computed!"; + } + // test if the value passes the type test var Type = Miso.types[c.type]; @@ -7023,11 +8087,28 @@ Version 0.0.1.2 } else { throw("incorrect value '" + props[c.name] + "' of type " + Miso.typeOf(props[c.name], c) + - " passed to column with type " + c.type); + " passed to column '" + c.name + "' with type " + c.type); } } c.data[rowIndex] = props[c.name]; }, this); + + // do we have any computed columns? if so we need to update + // the row. + if (typeof this._computedColumns !== "undefined") { + _.each(this._computedColumns, function(column) { + + // compute the complete row: + var newrow = _.extend({}, row, props); + + var oldValue = newrow[column.name]; + var newValue = column.compute(newrow, rowIndex); + // if this is actually a new value, then add it to the delta. + if (oldValue !== newValue) { + props[column.name] = newValue; + } + }); + } deltas.push( { _id : row._id, old : row, changed : props } ); }, this); @@ -7516,7 +8597,7 @@ Version 0.0.1.2 type : "GET", async : true, xhr : function() { - return new global.XMLHttpRequest(); + return global.ActiveXObject ? new global.ActiveXObject("Microsoft.XMLHTTP") : new global.XMLHttpRequest(); } }, rparams = /\?/; diff --git a/dist/miso.ds.deps.ie.0.2.1.js b/dist/miso.ds.deps.ie.0.2.2.js similarity index 75% rename from dist/miso.ds.deps.ie.0.2.1.js rename to dist/miso.ds.deps.ie.0.2.2.js index 6e7b8e8..d04bcf1 100644 --- a/dist/miso.ds.deps.ie.0.2.1.js +++ b/dist/miso.ds.deps.ie.0.2.2.js @@ -1,5 +1,5 @@ /** -* Miso.Dataset - v0.2.1 - 8/16/2012 +* Miso.Dataset - v0.2.2 - 9/3/2012 * http://github.com/misoproject/dataset * Copyright (c) 2012 Alex Graul, Irene Ros; * Dual Licensed: MIT, GPL @@ -8,31 +8,40 @@ */ // moment.js -// version : 1.6.2 +// version : 1.7.0 // author : Tim Wood // license : MIT // momentjs.com (function (Date, undefined) { + /************************************ + Constants + ************************************/ + var moment, - VERSION = "1.6.2", + VERSION = "1.7.0", round = Math.round, i, // internal storage for language config files languages = {}, currentLanguage = 'en', // check for nodeJS - hasModule = (typeof module !== 'undefined'), + hasModule = (typeof module !== 'undefined' && module.exports), - // parameters to check for on the lang config - langConfigProperties = 'months|monthsShort|monthsParse|weekdays|weekdaysShort|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'), + // Parameters to check for on the lang config. This list of properties + // will be inherited from English if not provided in a language + // definition. monthsParse is also a lang config property, but it + // cannot be inherited and as such cannot be enumerated here. + langConfigProperties = 'months|monthsShort|weekdays|weekdaysShort|weekdaysMin|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'), // ASP.NET json date format regex aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, // format tokens - formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|dddd?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?|LT|LL?L?L?)/g, + formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?)/g, + localFormattingTokens = /(LT|LL?L?L?)/g, + formattingRemoveEscapes = /(^\[)|(\\)|\]$/g, // parsing tokens parseMultipleFormatChunker = /([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi, @@ -41,7 +50,7 @@ parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 parseTokenThreeDigits = /\d{3}/, // 000 - 999 - parseTokenFourDigits = /\d{4}/, // 0000 - 9999 + parseTokenFourDigits = /\d{1,4}/, // 0 - 9999 parseTokenWord = /[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i, // any word characters or numbers parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z parseTokenT = /T/i, // T (ISO seperator) @@ -72,20 +81,77 @@ 'Days' : 864e5, 'Months' : 2592e6, 'Years' : 31536e6 - }; + }, + + // format function strings + formatFunctions = {}, + + /* + * moment.fn.format uses new Function() to create an inlined formatting function. + * Results are a 3x speed boost + * http://jsperf.com/momentjs-cached-format-functions + * + * These strings are appended into a function using replaceFormatTokens and makeFormatFunction + */ + formatFunctionStrings = { + // a = placeholder + // b = placeholder + // t = the current moment being formatted + // v = getValueAtKey function + // o = language.ordinal function + // p = leftZeroFill function + // m = language.meridiem value or function + M : '(a=t.month()+1)', + MMM : 'v("monthsShort",t.month())', + MMMM : 'v("months",t.month())', + D : '(a=t.date())', + DDD : '(a=new Date(t.year(),t.month(),t.date()),b=new Date(t.year(),0,1),a=~~(((a-b)/864e5)+1.5))', + d : '(a=t.day())', + dd : 'v("weekdaysMin",t.day())', + ddd : 'v("weekdaysShort",t.day())', + dddd : 'v("weekdays",t.day())', + w : '(a=new Date(t.year(),t.month(),t.date()-t.day()+5),b=new Date(a.getFullYear(),0,4),a=~~((a-b)/864e5/7+1.5))', + YY : 'p(t.year()%100,2)', + YYYY : 'p(t.year(),4)', + a : 'm(t.hours(),t.minutes(),!0)', + A : 'm(t.hours(),t.minutes(),!1)', + H : 't.hours()', + h : 't.hours()%12||12', + m : 't.minutes()', + s : 't.seconds()', + S : '~~(t.milliseconds()/100)', + SS : 'p(~~(t.milliseconds()/10),2)', + SSS : 'p(t.milliseconds(),3)', + Z : '((a=-t.zone())<0?((a=-a),"-"):"+")+p(~~(a/60),2)+":"+p(~~a%60,2)', + ZZ : '((a=-t.zone())<0?((a=-a),"-"):"+")+p(~~(10*a/6),4)' + }, + + ordinalizeTokens = 'DDD w M D d'.split(' '), + paddedTokens = 'M D H h m s w'.split(' '); + + while (ordinalizeTokens.length) { + i = ordinalizeTokens.pop(); + formatFunctionStrings[i + 'o'] = formatFunctionStrings[i] + '+o(a)'; + } + while (paddedTokens.length) { + i = paddedTokens.pop(); + formatFunctionStrings[i + i] = 'p(' + formatFunctionStrings[i] + ',2)'; + } + formatFunctionStrings.DDDD = 'p(' + formatFunctionStrings.DDD + ',3)'; + + + /************************************ + Constructors + ************************************/ + // Moment prototype object - function Moment(date, isUTC) { + function Moment(date, isUTC, lang) { this._d = date; this._isUTC = !!isUTC; - } - - function absRound(number) { - if (number < 0) { - return Math.ceil(number); - } else { - return Math.floor(number); - } + this._a = date._a || null; + date._a = null; + this._lang = lang || false; } // Duration Constructor @@ -138,6 +204,22 @@ years += absRound(months / 12); data.years = years; + + this._lang = false; + } + + + /************************************ + Helpers + ************************************/ + + + function absRound(number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } } // left zero fill a number @@ -176,141 +258,152 @@ return Object.prototype.toString.call(input) === '[object Array]'; } + // compare two arrays, return the number of differences + function compareArrays(array1, array2) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if (~~array1[i] !== ~~array2[i]) { + diffs++; + } + } + return diffs + lengthDiff; + } + // convert an array to a date. // the array should mirror the parameters below // note: all values past the year are optional and will default to the lowest possible value. // [year, month, day , hour, minute, second, millisecond] - function dateFromArray(input) { - return new Date(input[0], input[1] || 0, input[2] || 1, input[3] || 0, input[4] || 0, input[5] || 0, input[6] || 0); + function dateFromArray(input, asUTC) { + var i, date; + for (i = 1; i < 7; i++) { + input[i] = (input[i] == null) ? (i === 2 ? 1 : 0) : input[i]; + } + // we store whether we used utc or not in the input array + input[7] = asUTC; + date = new Date(0); + if (asUTC) { + date.setUTCFullYear(input[0], input[1], input[2]); + date.setUTCHours(input[3], input[4], input[5], input[6]); + } else { + date.setFullYear(input[0], input[1], input[2]); + date.setHours(input[3], input[4], input[5], input[6]); + } + date._a = input; + return date; + } + + // Loads a language definition into the `languages` cache. The function + // takes a key and optionally values. If not in the browser and no values + // are provided, it will load the language file module. As a convenience, + // this function also returns the language values. + function loadLang(key, values) { + var i, m, + parse = []; + + if (!values && hasModule) { + values = require('./lang/' + key); + } + + for (i = 0; i < langConfigProperties.length; i++) { + // If a language definition does not provide a value, inherit + // from English + values[langConfigProperties[i]] = values[langConfigProperties[i]] || + languages.en[langConfigProperties[i]]; + } + + for (i = 0; i < 12; i++) { + m = moment([2000, i]); + parse[i] = new RegExp('^' + (values.months[i] || values.months(m, '')) + + '|^' + (values.monthsShort[i] || values.monthsShort(m, '')).replace('.', ''), 'i'); + } + values.monthsParse = values.monthsParse || parse; + + languages[key] = values; + + return values; + } + + // Determines which language definition to use and returns it. + // + // With no parameters, it will return the global language. If you + // pass in a language key, such as 'en', it will return the + // definition for 'en', so long as 'en' has already been loaded using + // moment.lang. If you pass in a moment or duration instance, it + // will decide the language based on that, or default to the global + // language. + function getLangDefinition(m) { + var langKey = (typeof m === 'string') && m || + m && m._lang || + null; + + return langKey ? (languages[langKey] || loadLang(langKey)) : moment; + } + + + /************************************ + Formatting + ************************************/ + + + // helper for building inline formatting functions + function replaceFormatTokens(token) { + return formatFunctionStrings[token] ? + ("'+(" + formatFunctionStrings[token] + ")+'") : + token.replace(formattingRemoveEscapes, "").replace(/\\?'/g, "\\'"); + } + + // helper for recursing long date formatting tokens + function replaceLongDateFormatTokens(input) { + return getLangDefinition().longDateFormat[input] || input; + } + + function makeFormatFunction(format) { + var output = "var a,b;return '" + + format.replace(formattingTokens, replaceFormatTokens) + "';", + Fn = Function; // get around jshint + // t = the current moment being formatted + // v = getValueAtKey function + // o = language.ordinal function + // p = leftZeroFill function + // m = language.meridiem value or function + return new Fn('t', 'v', 'o', 'p', 'm', output); + } + + function makeOrGetFormatFunction(format) { + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } + return formatFunctions[format]; } // format date using native date object - function formatMoment(m, inputString) { - var currentMonth = m.month(), - currentDate = m.date(), - currentYear = m.year(), - currentDay = m.day(), - currentHours = m.hours(), - currentMinutes = m.minutes(), - currentSeconds = m.seconds(), - currentMilliseconds = m.milliseconds(), - currentZone = -m.zone(), - ordinal = moment.ordinal, - meridiem = moment.meridiem; - // check if the character is a format - // return formatted string or non string. - // - // uses switch/case instead of an object of named functions (like http://phpjs.org/functions/date:380) - // for minification and performance - // see http://jsperf.com/object-of-functions-vs-switch for performance comparison - function replaceFunction(input) { - // create a couple variables to be used later inside one of the cases. - var a, b; - switch (input) { - // MONTH - case 'M' : - return currentMonth + 1; - case 'Mo' : - return (currentMonth + 1) + ordinal(currentMonth + 1); - case 'MM' : - return leftZeroFill(currentMonth + 1, 2); - case 'MMM' : - return moment.monthsShort[currentMonth]; - case 'MMMM' : - return moment.months[currentMonth]; - // DAY OF MONTH - case 'D' : - return currentDate; - case 'Do' : - return currentDate + ordinal(currentDate); - case 'DD' : - return leftZeroFill(currentDate, 2); - // DAY OF YEAR - case 'DDD' : - a = new Date(currentYear, currentMonth, currentDate); - b = new Date(currentYear, 0, 1); - return ~~ (((a - b) / 864e5) + 1.5); - case 'DDDo' : - a = replaceFunction('DDD'); - return a + ordinal(a); - case 'DDDD' : - return leftZeroFill(replaceFunction('DDD'), 3); - // WEEKDAY - case 'd' : - return currentDay; - case 'do' : - return currentDay + ordinal(currentDay); - case 'ddd' : - return moment.weekdaysShort[currentDay]; - case 'dddd' : - return moment.weekdays[currentDay]; - // WEEK OF YEAR - case 'w' : - a = new Date(currentYear, currentMonth, currentDate - currentDay + 5); - b = new Date(a.getFullYear(), 0, 4); - return ~~ ((a - b) / 864e5 / 7 + 1.5); - case 'wo' : - a = replaceFunction('w'); - return a + ordinal(a); - case 'ww' : - return leftZeroFill(replaceFunction('w'), 2); - // YEAR - case 'YY' : - return leftZeroFill(currentYear % 100, 2); - case 'YYYY' : - return currentYear; - // AM / PM - case 'a' : - return meridiem ? meridiem(currentHours, currentMinutes, false) : (currentHours > 11 ? 'pm' : 'am'); - case 'A' : - return meridiem ? meridiem(currentHours, currentMinutes, true) : (currentHours > 11 ? 'PM' : 'AM'); - // 24 HOUR - case 'H' : - return currentHours; - case 'HH' : - return leftZeroFill(currentHours, 2); - // 12 HOUR - case 'h' : - return currentHours % 12 || 12; - case 'hh' : - return leftZeroFill(currentHours % 12 || 12, 2); - // MINUTE - case 'm' : - return currentMinutes; - case 'mm' : - return leftZeroFill(currentMinutes, 2); - // SECOND - case 's' : - return currentSeconds; - case 'ss' : - return leftZeroFill(currentSeconds, 2); - // MILLISECONDS - case 'S' : - return ~~ (currentMilliseconds / 100); - case 'SS' : - return leftZeroFill(~~(currentMilliseconds / 10), 2); - case 'SSS' : - return leftZeroFill(currentMilliseconds, 3); - // TIMEZONE - case 'Z' : - return (currentZone < 0 ? '-' : '+') + leftZeroFill(~~(Math.abs(currentZone) / 60), 2) + ':' + leftZeroFill(~~(Math.abs(currentZone) % 60), 2); - case 'ZZ' : - return (currentZone < 0 ? '-' : '+') + leftZeroFill(~~(10 * Math.abs(currentZone) / 6), 4); - // LONG DATES - case 'L' : - case 'LL' : - case 'LLL' : - case 'LLLL' : - case 'LT' : - return formatMoment(m, moment.longDateFormat[input]); - // DEFAULT - default : - return input.replace(/(^\[)|(\\)|\]$/g, ""); - } + function formatMoment(m, format) { + var lang = getLangDefinition(m); + + function getValueFromArray(key, index) { + return lang[key].call ? lang[key](m, format) : lang[key][index]; + } + + while (localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + } + + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); } - return inputString.replace(formattingTokens, replaceFunction); + + return formatFunctions[format](m, getValueFromArray, lang.ordinal, leftZeroFill, lang.meridiem); } + + /************************************ + Parsing + ************************************/ + + // get the regex to find the next token function getParseRegexForToken(token) { switch (token) { @@ -325,6 +418,7 @@ return parseTokenOneToThreeDigits; case 'MMM': case 'MMMM': + case 'dd': case 'ddd': case 'dddd': case 'a': @@ -337,7 +431,6 @@ return parseTokenT; case 'MM': case 'DD': - case 'dd': case 'YY': case 'HH': case 'hh': @@ -369,7 +462,7 @@ case 'MMM' : // fall through to MMMM case 'MMMM' : for (a = 0; a < 12; a++) { - if (moment.monthsParse[a].test(input)) { + if (getLangDefinition().monthsParse[a].test(input)) { datePartArray[1] = a; break; } @@ -380,7 +473,9 @@ case 'DD' : // fall through to DDDD case 'DDD' : // fall through to DDDD case 'DDDD' : - datePartArray[2] = ~~input; + if (input != null) { + datePartArray[2] = ~~input; + } break; // YEAR case 'YY' : @@ -465,21 +560,7 @@ datePartArray[3] += config.tzh; datePartArray[4] += config.tzm; // return - return config.isUTC ? new Date(Date.UTC.apply({}, datePartArray)) : dateFromArray(datePartArray); - } - - // compare two arrays, return the number of differences - function compareArrays(array1, array2) { - var len = Math.min(array1.length, array2.length), - lengthDiff = Math.abs(array1.length - array2.length), - diffs = 0, - i; - for (i = 0; i < len; i++) { - if (~~array1[i] !== ~~array2[i]) { - diffs++; - } - } - return diffs + lengthDiff; + return dateFromArray(datePartArray, config.isUTC); } // date from string and array of format strings @@ -521,15 +602,21 @@ return new Date(string); } + + /************************************ + Relative Time + ************************************/ + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture) { - var rt = moment.relativeTime[string]; + function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { + var rt = lang.relativeTime[string]; return (typeof rt === 'function') ? rt(number || 1, !!withoutSuffix, string, isFuture) : rt.replace(/%d/i, number || 1); } - function relativeTime(milliseconds, withoutSuffix) { + function relativeTime(milliseconds, withoutSuffix, lang) { var seconds = round(Math.abs(milliseconds) / 1000), minutes = round(seconds / 60), hours = round(minutes / 60), @@ -547,20 +634,25 @@ years === 1 && ['y'] || ['yy', years]; args[2] = withoutSuffix; args[3] = milliseconds > 0; + args[4] = lang; return substituteTimeAgo.apply({}, args); } + + /************************************ + Top Level Functions + ************************************/ + + moment = function (input, format) { if (input === null || input === '') { return null; } var date, - matched, - isUTC; + matched; // parse Moment object if (moment.isMoment(input)) { - date = new Date(+input._d); - isUTC = input._isUTC; + return new Moment(new Date(+input._d), input._isUTC, input._lang); // parse string and format } else if (format) { if (isArray(format)) { @@ -578,17 +670,23 @@ typeof input === 'string' ? makeDateFromString(input) : new Date(input); } - return new Moment(date, isUTC); + + return new Moment(date); }; // creating with utc moment.utc = function (input, format) { if (isArray(input)) { - return new Moment(new Date(Date.UTC.apply({}, input)), true); + return new Moment(dateFromArray(input, true), true); + } + // if we don't have a timezone, we need to add one to trigger parsing into utc + if (typeof input === 'string' && !parseTokenTimezone.exec(input)) { + input += ' +0000'; + if (format) { + format += ' Z'; + } } - return (format && input) ? - moment(input + ' +0000', format + ' Z').utc() : - moment(input && !parseTokenTimezone.exec(input) ? input + '+0000' : input).utc(); + return moment(input, format).utc(); }; // creating with unix timestamp (in seconds) @@ -600,7 +698,8 @@ moment.duration = function (input, key) { var isDuration = moment.isDuration(input), isNumber = (typeof input === 'number'), - duration = (isDuration ? input._data : (isNumber ? {} : input)); + duration = (isDuration ? input._data : (isNumber ? {} : input)), + ret; if (isNumber) { if (key) { @@ -610,7 +709,13 @@ } } - return new Duration(duration); + ret = new Duration(duration); + + if (isDuration) { + ret._lang = input._lang; + } + + return ret; }; // humanizeDuration @@ -626,40 +731,49 @@ // default format moment.defaultFormat = isoFormat; - // language switching and caching + // This function will load languages and then set the global language. If + // no arguments are passed in, it will simply return the current global + // language key. moment.lang = function (key, values) { - var i, req, - parse = []; + var i; + if (!key) { return currentLanguage; } - if (values) { - for (i = 0; i < 12; i++) { - parse[i] = new RegExp('^' + values.months[i] + '|^' + values.monthsShort[i].replace('.', ''), 'i'); - } - values.monthsParse = values.monthsParse || parse; - languages[key] = values; + if (values || !languages[key]) { + loadLang(key, values); } if (languages[key]) { + // deprecated, to get the language definition variables, use the + // moment.fn.lang method or the getLangDefinition function. for (i = 0; i < langConfigProperties.length; i++) { - moment[langConfigProperties[i]] = languages[key][langConfigProperties[i]] || - languages.en[langConfigProperties[i]]; + moment[langConfigProperties[i]] = languages[key][langConfigProperties[i]]; } + moment.monthsParse = languages[key].monthsParse; currentLanguage = key; - } else { - if (hasModule) { - req = require('./lang/' + key); - moment.lang(key, req); - } } }; - // set default language + // returns language data + moment.langData = getLangDefinition; + + // compare moment object + moment.isMoment = function (obj) { + return obj instanceof Moment; + }; + + // for typechecking Duration objects + moment.isDuration = function (obj) { + return obj instanceof Duration; + }; + + // Set default language, other languages will inherit from English. moment.lang('en', { months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), longDateFormat : { LT : "h:mm A", L : "MM/DD/YYYY", @@ -667,7 +781,13 @@ LLL : "MMMM D YYYY LT", LLLL : "dddd, MMMM D YYYY LT" }, - meridiem : false, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + }, calendar : { sameDay : '[Today at] LT', nextDay : '[Tomorrow at] LT', @@ -700,17 +820,12 @@ } }); - // compare moment object - moment.isMoment = function (obj) { - return obj instanceof Moment; - }; - // for typechecking Duration objects - moment.isDuration = function (obj) { - return obj instanceof Duration; - }; + /************************************ + Moment Prototype + ************************************/ + - // shortcut for prototype moment.fn = Moment.prototype = { clone : function () { @@ -733,6 +848,27 @@ return this._d; }, + toArray : function () { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hours(), + m.minutes(), + m.seconds(), + m.milliseconds(), + !!this._isUTC + ]; + }, + + isValid : function () { + if (this._a) { + return !compareArrays(this._a, (this._a[7] ? moment.utc(this) : this).toArray()); + } + return !isNaN(this._d.getTime()); + }, + utc : function () { this._isUTC = true; return this; @@ -783,7 +919,7 @@ }, from : function (time, withoutSuffix) { - return moment.duration(this.diff(time)).humanize(!withoutSuffix); + return moment.duration(this.diff(time)).lang(this._lang).humanize(!withoutSuffix); }, fromNow : function (withoutSuffix) { @@ -792,7 +928,7 @@ calendar : function () { var diff = this.diff(moment().sod(), 'days', true), - calendar = moment.calendar, + calendar = this.lang().calendar, allElse = calendar.sameElse, format = diff < -6 ? allElse : diff < -1 ? calendar.lastWeek : @@ -819,20 +955,43 @@ this.add({ d : input - day }); }, + startOf: function (val) { + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (val.replace(/s$/, '')) { + case 'year': + this.month(0); + /* falls through */ + case 'month': + this.date(1); + /* falls through */ + case 'day': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + /* falls through */ + } + return this; + }, + + endOf: function (val) { + return this.startOf(val).add(val.replace(/s?$/, 's'), 1).subtract('ms', 1); + }, + sod: function () { - return moment(this) - .hours(0) - .minutes(0) - .seconds(0) - .milliseconds(0); + return this.clone().startOf('day'); }, eod: function () { // end of day = start of day plus 1 day, minus 1 millisecond - return this.sod().add({ - d : 1, - ms : -1 - }); + return this.clone().endOf('day'); }, zone : function () { @@ -840,7 +999,19 @@ }, daysInMonth : function () { - return moment(this).month(this.month() + 1).date(0).date(); + return moment.utc([this.year(), this.month() + 1, 0]).date(); + }, + + // If passed a language key, it will set the language for this + // instance. Otherwise, it will return the language configuration + // variables for this instance. + lang : function (lang) { + if (lang === undefined) { + return getLangDefinition(this); + } else { + this._lang = lang; + return this; + } } }; @@ -865,6 +1036,12 @@ // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') makeGetterAndSetter('year', 'FullYear'); + + /************************************ + Duration Prototype + ************************************/ + + moment.duration.fn = Duration.prototype = { weeks : function () { return absRound(this.days() / 7); @@ -878,15 +1055,17 @@ humanize : function (withSuffix) { var difference = +this, - rel = moment.relativeTime, - output = relativeTime(difference, !withSuffix); + rel = this.lang().relativeTime, + output = relativeTime(difference, !withSuffix, this.lang()); if (withSuffix) { output = (difference <= 0 ? rel.past : rel.future).replace(/%s/i, output); } return output; - } + }, + + lang : moment.fn.lang }; function makeDurationGetter(name) { @@ -910,13 +1089,22 @@ makeDurationAsGetter('Weeks', 6048e5); + + /************************************ + Exposing Moment + ************************************/ + + // CommonJS module is defined if (hasModule) { module.exports = moment; } /*global ender:false */ - if (typeof window !== 'undefined' && typeof ender === 'undefined') { - window.moment = moment; + if (typeof ender === 'undefined') { + // here, `this` means `window` in the browser, or `global` on the server + // add `moment` as a global object via a string identifier, + // for Closure Compiler "advanced" mode + this['moment'] = moment; } /*global define:false */ if (typeof define === "function" && define.amd) { @@ -924,9 +1112,9 @@ return moment; }); } -})(Date); +}).call(this, Date); /*! - * Lo-Dash v0.3.2 + * Lo-Dash v0.6.1 * Copyright 2012 John-David Dalton * Based on Underscore.js 1.3.3, copyright 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. * @@ -935,33 +1123,80 @@ ;(function(window, undefined) { 'use strict'; + /** + * Used to cache the last `_.templateSettings.evaluate` delimiter to avoid + * unnecessarily assigning `reEvaluateDelimiter` a new generated regexp. + * Assigned in `_.template`. + */ + var lastEvaluateDelimiter; + + /** + * Used to cache the last template `options.variable` to avoid unnecessarily + * assigning `reDoubleVariable` a new generated regexp. Assigned in `_.template`. + */ + var lastVariable; + + /** + * Used to match potentially incorrect data object references, like `obj.obj`, + * in compiled templates. Assigned in `_.template`. + */ + var reDoubleVariable; + + /** + * Used to match "evaluate" delimiters, including internal delimiters, + * in template text. Assigned in `_.template`. + */ + var reEvaluateDelimiter; + /** Detect free variable `exports` */ var freeExports = typeof exports == 'object' && exports && (typeof global == 'object' && global && global == global.global && (window = global), exports); - /** - * Detect the JScript [[DontEnum]] bug: - * In IE < 9 an objects own properties, shadowing non-enumerable ones, are - * made non-enumerable as well. - */ - var hasDontEnumBug = !{ 'valueOf': 0 }.propertyIsEnumerable('valueOf'); + /** Native prototype shortcuts */ + var ArrayProto = Array.prototype, + BoolProto = Boolean.prototype, + ObjectProto = Object.prototype, + NumberProto = Number.prototype, + StringProto = String.prototype; /** Used to generate unique IDs */ var idCounter = 0; + /** Used by `cachedContains` as the default size when optimizations are enabled for large arrays */ + var largeArraySize = 30; + /** Used to restore the original `_` reference in `noConflict` */ var oldDash = window._; + /** Used to detect delimiter values that should be processed by `tokenizeEvaluate` */ + var reComplexDelimiter = /[-+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/; + + /** Used to match HTML entities */ + var reEscapedHtml = /&(?:amp|lt|gt|quot|#x27);/g; + + /** 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 regexp flags from their coerced string values */ + var reFlags = /\w*$/; + + /** Used to insert the data object variable into compiled template source */ + var reInsertVariable = /(?:__e|__t = )\(\s*(?![\d\s"']|this\.)/g; + /** Used to detect if a method is native */ - var reNative = RegExp('^' + ({}.valueOf + '') - .replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&') - .replace(/valueOf|for [^\]]+/g, '.+?') + '$'); + var reNative = RegExp('^' + + (ObjectProto.valueOf + '') + .replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&') + .replace(/valueOf|for [^\]]+/g, '.+?') + '$' + ); - /** Used to match tokens in template text */ + /** Used to match internally used tokens in template text */ var reToken = /__token__(\d+)/g; - /** Used to match unescaped characters in HTML */ - var reUnescapedHtml = /[&<"']/g; + /** 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; @@ -981,25 +1216,162 @@ /** Used to store tokenized template text snippets */ var tokenized = []; - /** Detect if sourceURL syntax is usable without erroring */ + /** Native method shortcuts */ + var concat = ArrayProto.concat, + hasOwnProperty = ObjectProto.hasOwnProperty, + push = ArrayProto.push, + propertyIsEnumerable = ObjectProto.propertyIsEnumerable, + slice = ArrayProto.slice, + toString = ObjectProto.toString; + + /* Native method shortcuts for methods with the same name as other `lodash` methods */ + var nativeBind = reNative.test(nativeBind = slice.bind) && nativeBind, + nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, + nativeIsFinite = window.isFinite, + nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys; + + /** `Object#toString` result shortcuts */ + var argsClass = '[object Arguments]', + arrayClass = '[object Array]', + boolClass = '[object Boolean]', + dateClass = '[object Date]', + funcClass = '[object Function]', + numberClass = '[object Number]', + objectClass = '[object Object]', + regexpClass = '[object RegExp]', + stringClass = '[object String]'; + + /** Timer shortcuts */ + var clearTimeout = window.clearTimeout, + setTimeout = window.setTimeout; + + /** + * Detect the JScript [[DontEnum]] bug: + * + * In IE < 9 an objects own properties, shadowing non-enumerable ones, are + * made non-enumerable as well. + */ + var hasDontEnumBug; + + /** + * Detect if `Array#shift` and `Array#splice` augment array-like objects + * incorrectly: + * + * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()` + * and `splice()` functions that fail to remove the last element, `value[0]`, + * of array-like objects even though the `length` property is set to `0`. + * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` + * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. + */ + var hasObjectSpliceBug; + + /** Detect if own properties are iterated after inherited properties (IE < 9) */ + var iteratesOwnLast; + + /** Detect if an `arguments` object's indexes are non-enumerable (IE < 9) */ + var noArgsEnum = true; + + (function() { + var object = { '0': 1, 'length': 1 }, + props = []; + + function ctor() { this.x = 1; } + ctor.prototype = { 'valueOf': 1, 'y': 1 }; + for (var prop in new ctor) { props.push(prop); } + for (prop in arguments) { noArgsEnum = !prop; } + + hasDontEnumBug = (props + '').length < 4; + iteratesOwnLast = props[0] != 'x'; + hasObjectSpliceBug = (props.splice.call(object, 0, 1), object[0]); + }(1)); + + /** Detect if an `arguments` object's [[Class]] is unresolvable (Firefox < 4, IE < 9) */ + var noArgsClass = !isArguments(arguments); + + /** Detect if `Array#slice` cannot be used to convert strings to arrays (Opera < 10.52) */ + var noArraySliceOnStrings = slice.call('x')[0] != 'x'; + + /** + * Detect lack of support for accessing string characters by index: + * + * IE < 8 can't access characters by index and IE 8 can only access + * characters by index on string literals. + */ + var noCharByIndex = ('x'[0] + Object('x')[0]) != 'xx'; + + /** + * Detect if a node's [[Class]] is unresolvable (IE < 9) + * and that the JS engine won't error when attempting to coerce an object to + * a string without a `toString` property value of `typeof` "function". + */ + try { + var noNodeClass = ({ 'toString': 0 } + '', toString.call(window.document || 0) == objectClass); + } catch(e) { } + + /* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */ + var isBindFast = nativeBind && /\n|Opera/.test(nativeBind + toString.call(window.opera)); + + /* Detect if `Object.keys` exists and is inferred to be fast (IE, Opera, V8) */ + var isKeysFast = nativeKeys && /^.+$|true/.test(nativeKeys + !!window.attachEvent); + + /* Detect if strict mode, "use strict", is inferred to be fast (V8) */ + var isStrictFast = !isBindFast; + + /** + * Detect if sourceURL syntax is usable without erroring: + * + * The JS engine in Adobe products, like InDesign, will throw a syntax error + * when it encounters a single line comment beginning with the `@` symbol. + * + * The JS engine in Narwhal will generate the function `function anonymous(){//}` + * and throw a syntax error. + * + * Avoid comments beginning `@` symbols in IE because they are part of its + * non-standard conditional compilation support. + * http://msdn.microsoft.com/en-us/library/121hztk3(v=vs.94).aspx + */ try { - // Adobe's and Narwhal's JS engines will error - var useSourceURL = (Function('//@')(), true); + var useSourceURL = (Function('//@')(), !window.attachEvent); } catch(e){ } + /** Used to identify object classifications that are array-like */ + var arrayLikeClasses = {}; + arrayLikeClasses[boolClass] = arrayLikeClasses[dateClass] = arrayLikeClasses[funcClass] = + arrayLikeClasses[numberClass] = arrayLikeClasses[objectClass] = arrayLikeClasses[regexpClass] = false; + arrayLikeClasses[argsClass] = arrayLikeClasses[arrayClass] = arrayLikeClasses[stringClass] = true; + + /** Used to identify object classifications that `_.clone` supports */ + var cloneableClasses = {}; + cloneableClasses[argsClass] = cloneableClasses[funcClass] = false; + cloneableClasses[arrayClass] = cloneableClasses[boolClass] = cloneableClasses[dateClass] = + cloneableClasses[numberClass] = cloneableClasses[objectClass] = cloneableClasses[regexpClass] = + cloneableClasses[stringClass] = true; + /** - * Used to escape characters for inclusion in HTML. - * The `>` and `/` characters 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 (semi-related fun fact) + * 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 = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': "'" + }; + /** Used to determine if values are of the language type Object */ var objectTypes = { 'boolean': false, @@ -1007,7 +1379,8 @@ 'object': true, 'number': false, 'string': false, - 'undefined': false + 'undefined': false, + 'unknown': true }; /** Used to escape characters for inclusion in compiled string literals */ @@ -1021,39 +1394,6 @@ '\u2029': 'u2029' }; - /** Object#toString result shortcuts */ - var arrayClass = '[object Array]', - boolClass = '[object Boolean]', - dateClass = '[object Date]', - funcClass = '[object Function]', - numberClass = '[object Number]', - regexpClass = '[object RegExp]', - stringClass = '[object String]'; - - /** Native prototype shortcuts */ - var ArrayProto = Array.prototype, - ObjectProto = Object.prototype; - - /** Native method shortcuts */ - var concat = ArrayProto.concat, - hasOwnProperty = ObjectProto.hasOwnProperty, - push = ArrayProto.push, - slice = ArrayProto.slice, - toString = ObjectProto.toString; - - /* Used if `Function#bind` exists and is inferred to be fast (i.e. all but V8) */ - var nativeBind = reNative.test(nativeBind = slice.bind) && - /\n|Opera/.test(nativeBind + toString.call(window.opera)) && nativeBind; - - /* Native method shortcuts for methods with the same name as other `lodash` methods */ - var nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, - nativeIsFinite = window.isFinite, - nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys; - - /** Timer shortcuts */ - var clearTimeout = window.clearTimeout, - setTimeout = window.setTimeout; - /*--------------------------------------------------------------------------*/ /** @@ -1085,8 +1425,9 @@ } /** - * By default, Lo-Dash uses ERB-style template delimiters, change the - * following template settings to use alternative delimiters. + * 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 _ @@ -1128,7 +1469,7 @@ * @memberOf _.templateSettings * @type String */ - 'variable': 'obj' + 'variable': '' }; /*--------------------------------------------------------------------------*/ @@ -1141,8 +1482,13 @@ * @returns {String} Returns the interpolated text. */ var iteratorTemplate = template( + // conditional strict mode + '<% if (useStrict) { %>\'use strict\';\n<% } %>' + + + // the `iteratee` may be reassigned by the `top` snippet + 'var index, value, iteratee = <%= firstArg %>, ' + // assign the `result` variable an initial value - 'var index, result<% if (init) { %> = <%= init %><% } %>;\n' + + 'result<% if (init) { %> = <%= init %><% } %>;\n' + // add code to exit early or do so if the first argument is falsey '<%= exit %>;\n' + // add code after the exit snippet but before the iteration branches @@ -1150,29 +1496,38 @@ // the following branch is for iterating arrays and array-like objects '<% if (arrayBranch) { %>' + - 'var length = <%= firstArg %>.length; index = -1;' + - ' <% if (objectBranch) { %>\nif (length === length >>> 0) {<% } %>\n' + + 'var length = iteratee.length; index = -1;' + + ' <% if (objectBranch) { %>\nif (length > -1 && length === length >>> 0) {<% } %>' + + + // add support for accessing string characters by index if needed + ' <% if (noCharByIndex) { %>\n' + + ' if (toString.call(iteratee) == stringClass) {\n' + + ' iteratee = iteratee.split(\'\')\n' + + ' }' + + ' <% } %>\n' + + ' <%= arrayBranch.beforeLoop %>;\n' + - ' while (<%= arrayBranch.loopExp %>) {\n' + - ' <%= arrayBranch.inLoop %>;\n' + + ' while (++index < length) {\n' + + ' value = iteratee[index];\n' + + ' <%= arrayBranch.inLoop %>\n' + ' }' + - ' <% if (objectBranch) { %>\n}\n<% }' + - '}' + + ' <% if (objectBranch) { %>\n}<% } %>' + + '<% } %>' + // the following branch is for iterating an object's own/inherited properties - 'if (objectBranch) {' + - ' if (arrayBranch) { %>else {\n<% }' + - ' if (!hasDontEnumBug) { %> var skipProto = typeof <%= iteratedObject %> == \'function\';\n<% } %>' + - ' <%= objectBranch.beforeLoop %>;\n' + - ' for (<%= objectBranch.loopExp %>) {' + - ' \n<%' + - ' if (hasDontEnumBug) {' + - ' if (useHas) { %> if (<%= hasExp %>) {\n <% } %>' + - ' <%= objectBranch.inLoop %>;<%' + - ' if (useHas) { %>\n }<% }' + - ' }' + - ' else {' + - ' %>' + + '<% if (objectBranch) { %>' + + ' <% if (arrayBranch) { %>\nelse {' + + + // add support for iterating over `arguments` objects if needed + ' <% } else if (noArgsEnum) { %>\n' + + ' var length = iteratee.length; index = -1;\n' + + ' if (length && isArguments(iteratee)) {\n' + + ' while (++index < length) {\n' + + ' value = iteratee[index += \'\'];\n' + + ' <%= objectBranch.inLoop %>\n' + + ' }\n' + + ' } else {' + + ' <% } %>' + // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 // (if the prototype or a property on the prototype has been set) @@ -1180,30 +1535,60 @@ // value to `true`. Because of this Lo-Dash standardizes on skipping // the the `prototype` property of functions regardless of its // [[Enumerable]] value. - ' if (!(skipProto && index == \'prototype\')<% if (useHas) { %> && <%= hasExp %><% } %>) {\n' + - ' <%= objectBranch.inLoop %>;\n' + - ' }' + - ' <% } %>\n' + + ' <% if (!hasDontEnumBug) { %>\n' + + ' var skipProto = typeof iteratee == \'function\' && \n' + + ' propertyIsEnumerable.call(iteratee, \'prototype\');\n' + + ' <% } %>' + + + // iterate own properties using `Object.keys` if it's fast + ' <% if (isKeysFast && useHas) { %>\n' + + ' var ownIndex = -1,\n' + + ' ownProps = objectTypes[typeof iteratee] ? nativeKeys(iteratee) : [],\n' + + ' length = ownProps.length;\n\n' + + ' <%= objectBranch.beforeLoop %>;\n' + + ' while (++ownIndex < length) {\n' + + ' index = ownProps[ownIndex];\n' + + ' <% if (!hasDontEnumBug) { %>if (!(skipProto && index == \'prototype\')) {\n <% } %>' + + ' value = iteratee[index];\n' + + ' <%= objectBranch.inLoop %>\n' + + ' <% if (!hasDontEnumBug) { %>}\n<% } %>' + + ' }' + + + // else using a for-in loop + ' <% } else { %>\n' + + ' <%= objectBranch.beforeLoop %>;\n' + + ' for (index in iteratee) {' + + ' <% if (!hasDontEnumBug || useHas) { %>\n if (<%' + + ' if (!hasDontEnumBug) { %>!(skipProto && index == \'prototype\')<% }' + + ' if (!hasDontEnumBug && useHas) { %> && <% }' + + ' if (useHas) { %>hasOwnProperty.call(iteratee, index)<% }' + + ' %>) {' + + ' <% } %>\n' + + ' value = iteratee[index];\n' + + ' <%= objectBranch.inLoop %>;\n' + + ' <% if (!hasDontEnumBug || useHas) { %>}\n<% } %>' + ' }' + + ' <% } %>' + // Because IE < 9 can't set the `[[Enumerable]]` attribute of an // existing property and the `constructor` property of a prototype // defaults to non-enumerable, Lo-Dash skips the `constructor` // property when it infers it's iterating over a `prototype` object. - ' <% if (hasDontEnumBug) { %>\n' + - ' var ctor = <%= iteratedObject %>.constructor;\n' + - ' <% for (var k = 0; k < 7; k++) { %>\n' + + ' <% if (hasDontEnumBug) { %>\n\n' + + ' var ctor = iteratee.constructor;\n' + + ' <% for (var k = 0; k < 7; k++) { %>\n' + ' index = \'<%= shadowed[k] %>\';\n' + ' if (<%' + ' if (shadowed[k] == \'constructor\') {' + - ' %>!(ctor && ctor.prototype === <%= iteratedObject %>) && <%' + - ' } %><%= hasExp %>) {\n' + - ' <%= objectBranch.inLoop %>;\n' + - ' }<%' + - ' }' + - ' }' + - ' if (arrayBranch) { %>\n}<% }' + - '} %>\n' + + ' %>!(ctor && ctor.prototype === iteratee) && <%' + + ' } %>hasOwnProperty.call(iteratee, index)) {\n' + + ' value = iteratee[index];\n' + + ' <%= objectBranch.inLoop %>\n' + + ' }' + + ' <% } %>' + + ' <% } %>' + + ' <% if (arrayBranch || noArgsEnum) { %>\n}<% } %>' + + '<% } %>\n' + // add code to the bottom of the iteration function '<%= bottom %>;\n' + @@ -1213,7 +1598,8 @@ /** * Reusable iterator options shared by - * `every`, `filter`, `find`, `forEach`, `forIn`, `forOwn`, `map`, `reject`, and `some`. + * `every`, `filter`, `find`, `forEach`, `forIn`, `forOwn`, `groupBy`, `map`, + * `reject`, `some`, and `sortBy`. */ var baseIteratorOptions = { 'args': 'collection, callback, thisArg', @@ -1225,33 +1611,68 @@ 'else if (thisArg) {\n' + ' callback = iteratorBind(callback, thisArg)\n' + '}', - 'inLoop': 'callback(collection[index], index, collection)' + 'inLoop': 'if (callback(value, index, collection) === false) return result' + }; + + /** Reusable iterator options for `countBy`, `groupBy`, and `sortBy` */ + var countByIteratorOptions = { + 'init': '{}', + 'top': + 'var prop;\n' + + 'if (typeof callback != \'function\') {\n' + + ' var valueProp = callback;\n' + + ' callback = function(value) { return value[valueProp] }\n' + + '}\n' + + 'else if (thisArg) {\n' + + ' callback = iteratorBind(callback, thisArg)\n' + + '}', + 'inLoop': + 'prop = callback(value, index, collection);\n' + + '(hasOwnProperty.call(result, prop) ? result[prop]++ : result[prop] = 1)' + }; + + /** Reusable iterator options for `drop` and `pick` */ + var dropIteratorOptions = { + 'useHas': false, + 'args': 'object, callback, thisArg', + 'init': '{}', + 'top': + 'var isFunc = typeof callback == \'function\';\n' + + 'if (!isFunc) {\n' + + ' var props = concat.apply(ArrayProto, arguments)\n' + + '} else if (thisArg) {\n' + + ' callback = iteratorBind(callback, thisArg)\n' + + '}', + 'inLoop': + 'if (isFunc\n' + + ' ? !callback(value, index, object)\n' + + ' : indexOf(props, index) < 0\n' + + ') result[index] = value' }; /** Reusable iterator options for `every` and `some` */ var everyIteratorOptions = { 'init': 'true', - 'inLoop': 'if (!callback(collection[index], index, collection)) return !result' + 'inLoop': 'if (!callback(value, index, collection)) return !result' }; /** Reusable iterator options for `defaults` and `extend` */ var extendIteratorOptions = { + 'useHas': false, + 'useStrict': false, 'args': 'object', 'init': 'object', 'top': - 'for (var source, sourceIndex = 1, length = arguments.length; sourceIndex < length; sourceIndex++) {\n' + - ' source = arguments[sourceIndex];\n' + - (hasDontEnumBug ? ' if (source) {' : ''), - 'loopExp': 'index in source', - 'useHas': false, - 'inLoop': 'object[index] = source[index]', - 'bottom': (hasDontEnumBug ? ' }\n' : '') + '}' + 'for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {\n' + + ' if (iteratee = arguments[argsIndex]) {', + 'inLoop': 'result[index] = value', + 'bottom': ' }\n}' }; - /** Reusable iterator options for `filter` and `reject` */ + /** Reusable iterator options for `filter`, `reject`, and `where` */ var filterIteratorOptions = { 'init': '[]', - 'inLoop': 'callback(collection[index], index, collection) && result.push(collection[index])' + 'inLoop': 'callback(value, index, collection) && result.push(value)' }; /** Reusable iterator options for `find`, `forEach`, `forIn`, and `forOwn` */ @@ -1266,22 +1687,61 @@ } }; - /** Reusable iterator options for `invoke`, `map`, and `pluck` */ + /** Reusable iterator options for `invoke`, `map`, `pluck`, and `sortBy` */ var mapIteratorOptions = { 'init': '', 'exit': 'if (!collection) return []', 'beforeLoop': { 'array': 'result = Array(length)', - 'object': 'result = []' + 'object': 'result = ' + (isKeysFast ? 'Array(length)' : '[]') }, 'inLoop': { - 'array': 'result[index] = callback(collection[index], index, collection)', - 'object': 'result.push(callback(collection[index], index, collection))' + 'array': 'result[index] = callback(value, index, collection)', + 'object': 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '(callback(value, index, collection))' } }; /*--------------------------------------------------------------------------*/ + /** + * Creates a new function optimized for searching large arrays for a given `value`, + * starting at `fromIndex`, using strict equality for comparisons, i.e. `===`. + * + * @private + * @param {Array} array The array to search. + * @param {Mixed} value The value to search for. + * @param {Number} [fromIndex=0] The index to start searching from. + * @param {Number} [largeSize=30] The length at which an array is considered large. + * @returns {Boolean} Returns `true` if `value` is found, else `false`. + */ + function cachedContains(array, fromIndex, largeSize) { + fromIndex || (fromIndex = 0); + + var length = array.length, + isLarge = (length - fromIndex) >= (largeSize || largeArraySize), + cache = isLarge ? {} : array; + + if (isLarge) { + // init value cache + var key, + index = fromIndex - 1; + + while (++index < length) { + // manually coerce `value` to string because `hasOwnProperty`, in some + // older versions of Firefox, coerces objects incorrectly + key = array[index] + ''; + (hasOwnProperty.call(cache, key) ? cache[key] : (cache[key] = [])).push(array[index]); + } + } + return function(value) { + if (isLarge) { + var key = value + ''; + return hasOwnProperty.call(cache, key) && indexOf(cache[key], value) > -1; + } + return indexOf(cache, value, fromIndex) > -1; + } + } + /** * Creates compiled iteration functions. The iteration function will be created * to iterate over only objects if the first argument of `options.args` is @@ -1290,6 +1750,12 @@ * @private * @param {Object} [options1, options2, ...] The compile options objects. * + * useHas - A boolean to specify whether or not to use `hasOwnProperty` checks + * in the object loop. + * + * useStrict - A boolean to specify whether or not to include the ES5 + * "use strict" directive. + * * args - A string of comma separated arguments the iteration function will * accept. * @@ -1304,12 +1770,6 @@ * beforeLoop - A string or object containing an "array" or "object" property * of code to execute before the array or object loops. * - * loopExp - A string or object containing an "array" or "object" property - * of code to execute as the array or object loop expression. - * - * useHas - A boolean to specify whether or not to use `hasOwnProperty` checks - * in the object loop. - * * inLoop - A string or object containing an "array" or "object" property * of code to execute in the array or object loops. * @@ -1331,7 +1791,7 @@ 'exit': '', 'init': '', 'top': '', - 'arrayBranch': { 'beforeLoop': '', 'loopExp': '++index < length' }, + 'arrayBranch': { 'beforeLoop': '' }, 'objectBranch': { 'beforeLoop': '' } }; @@ -1340,12 +1800,12 @@ for (prop in object) { value = (value = object[prop]) == null ? '' : value; // keep this regexp explicit for the build pre-process - if (/beforeLoop|loopExp|inLoop/.test(prop)) { + if (/beforeLoop|inLoop/.test(prop)) { if (typeof value == 'string') { value = { 'array': value, 'object': value }; } - data.arrayBranch[prop] = value.array; - data.objectBranch[prop] = value.object; + data.arrayBranch[prop] = value.array || ''; + data.objectBranch[prop] = value.object || ''; } else { data[prop] = value; } @@ -1353,51 +1813,57 @@ } // set additional template `data` values var args = data.args, - arrayBranch = data.arrayBranch, - objectBranch = data.objectBranch, firstArg = /^[^,]+/.exec(args)[0], - loopExp = objectBranch.loopExp, - iteratedObject = /\S+$/.exec(loopExp || firstArg)[0]; + useStrict = data.useStrict; data.firstArg = firstArg; data.hasDontEnumBug = hasDontEnumBug; - data.hasExp = 'hasOwnProperty.call(' + iteratedObject + ', index)'; - data.iteratedObject = iteratedObject; + data.isKeysFast = isKeysFast; + data.noArgsEnum = noArgsEnum; data.shadowed = shadowed; data.useHas = data.useHas !== false; + data.useStrict = useStrict == null ? isStrictFast : useStrict; + if (data.noCharByIndex == null) { + data.noCharByIndex = noCharByIndex; + } if (!data.exit) { data.exit = 'if (!' + firstArg + ') return result'; } - if (firstArg == 'object' || !arrayBranch.inLoop) { + if (firstArg != 'collection' || !data.arrayBranch.inLoop) { data.arrayBranch = null; } - if (!loopExp) { - objectBranch.loopExp = 'index in ' + iteratedObject; - } // create the function factory var factory = Function( - 'arrayClass, funcClass, hasOwnProperty, identity, iteratorBind, objectTypes, ' + - 'slice, stringClass, toString', - '"use strict"; return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' + 'arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn, ' + + 'hasOwnProperty, identity, indexOf, isArguments, isArray, isFunction, ' + + 'isPlainObject, iteratorBind, objectClass, objectTypes, nativeKeys, ' + + 'propertyIsEnumerable, slice, stringClass, toString', + 'var callee = function(' + args + ') {\n' + iteratorTemplate(data) + '\n};\n' + + 'return callee' ); // return the compiled function return factory( - arrayClass, funcClass, hasOwnProperty, identity, iteratorBind, objectTypes, - slice, stringClass, toString + arrayLikeClasses, ArrayProto, bind, compareAscending, concat, forIn, + hasOwnProperty, identity, indexOf, isArguments, isArray, isFunction, + isPlainObject, iteratorBind, objectClass, objectTypes, nativeKeys, + propertyIsEnumerable, slice, stringClass, toString ); } /** - * Used by `sortBy()` to compare values of the array returned by `toSortable()`, - * sorting them in ascending order. + * 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 `-1` if `a` < `b`, `0` if `a` == `b`, or `1` if `a` > `b`. + * @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; @@ -1407,11 +1873,13 @@ if (b === undefined) { return -1; } - return a < b ? -1 : a > b ? 1 : 0; + // ensure a stable sort in V8 and other engines + // http://code.google.com/p/v8/issues/detail?id=90 + return a < b ? -1 : a > b ? 1 : ai < bi ? -1 : 1; } /** - * Used by `template()` to replace tokens with their corresponding code snippets. + * Used by `template` to replace tokens with their corresponding code snippets. * * @private * @param {String} match The matched token. @@ -1423,7 +1891,7 @@ } /** - * Used by `template()` to escape characters for inclusion in compiled + * Used by `template` to escape characters for inclusion in compiled * string literals. * * @private @@ -1435,7 +1903,7 @@ } /** - * Used by `escape()` to escape characters for inclusion in HTML. + * Used by `escape` to convert characters to HTML entities. * * @private * @param {String} match The matched character to escape. @@ -1470,22 +1938,7 @@ } /** - * A shim implementation of `Object.keys` that produces an array of the given - * object's own enumerable property names. - * - * @private - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property names. - */ - var shimKeys = createIterator({ - 'args': 'object', - 'exit': 'if (!objectTypes[typeof object] || object === null) throw TypeError()', - 'init': '[]', - 'inLoop': 'result.push(index)' - }); - - /** - * Used by `template()` to replace "escape" template delimiters with tokens. + * Used by `template` to replace "escape" template delimiters with tokens. * * @private * @param {String} match The matched template delimiter. @@ -1493,2424 +1946,2858 @@ * @returns {String} Returns a token. */ function tokenizeEscape(match, value) { + if (match && reComplexDelimiter.test(value)) { + return ''; + } var index = tokenized.length; - tokenized[index] = "'+\n_.escape(" + value + ") +\n'"; + tokenized[index] = "' +\n__e(" + value + ") +\n'"; return token + index; } /** - * Used by `template()` to replace "interpolate" template delimiters with tokens. + * Used by `template` to replace "evaluate" template delimiters, or complex + * "escape" and "interpolate" delimiters, with tokens. * * @private * @param {String} match The matched template delimiter. - * @param {String} value The delimiter value. + * @param {String} escapeValue The complex "escape" delimiter value. + * @param {String} interpolateValue The complex "interpolate" delimiter value. + * @param {String} [evaluateValue] The "evaluate" delimiter value. * @returns {String} Returns a token. */ - function tokenizeInterpolate(match, value) { - var index = tokenized.length; - tokenized[index] = "'+\n((__t = (" + value + ")) == null ? '' : __t) +\n'"; - return token + index; + function tokenizeEvaluate(match, escapeValue, interpolateValue, evaluateValue) { + if (evaluateValue) { + var index = tokenized.length; + tokenized[index] = "';\n" + evaluateValue + ";\n__p += '"; + return token + index; + } + return escapeValue + ? tokenizeEscape(null, escapeValue) + : tokenizeInterpolate(null, interpolateValue); } /** - * Used by `template()` to replace "evaluate" template delimiters with tokens. + * Used by `template` to replace "interpolate" template delimiters with tokens. * * @private * @param {String} match The matched template delimiter. * @param {String} value The delimiter value. * @returns {String} Returns a token. */ - function tokenizeEvaluate(match, value) { + function tokenizeInterpolate(match, value) { + if (match && reComplexDelimiter.test(value)) { + return ''; + } var index = tokenized.length; - tokenized[index] = "';\n" + value + ";\n__p += '"; + tokenized[index] = "' +\n((__t = (" + value + ")) == null ? '' : __t) +\n'"; return token + index; } /** - * Converts `collection` to an array of objects by running each element through - * a transformation `callback`. Each object has a `criteria` property containing - * the transformed value to be sorted and a `value` property containing the - * original unmodified value. The `callback` is invoked with 3 arguments; - * for arrays they are (value, index, array) and for objects they are - * (value, key, object). + * Used by `unescape` to convert HTML entities to characters. * * @private - * @param {Array|Object} collection The collection to convert. - * @param {Function} callback The function called per iteration. - * @returns {Array} Returns a new array of objects to sort. + * @param {String} match The matched character to unescape. + * @returns {String} Returns the unescaped character. */ - var toSortable = createIterator(mapIteratorOptions, { - 'args': 'collection, callback', - 'inLoop': { - 'array': - 'result[index] = {\n' + - ' criteria: callback(collection[index], index, collection),\n' + - ' value: collection[index]\n' + - '}', - 'object': - 'result.push({\n' + - ' criteria: callback(collection[index], index, collection),\n' + - ' value: collection[index]\n' + - '})' - } - }); + function unescapeHtmlChar(match) { + return htmlUnescapes[match]; + } /*--------------------------------------------------------------------------*/ /** - * Checks if a given `target` value is present in a `collection` using strict - * equality for comparisons, i.e. `===`. + * Checks if `value` is an `arguments` object. * * @static * @memberOf _ - * @alias include - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Mixed} target The value to check for. - * @returns {Boolean} Returns `true` if `target` value is found, else `false`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is an `arguments` object, else `false`. * @example * - * _.contains([1, 2, 3], 3); + * (function() { return _.isArguments(arguments); })(1, 2, 3); * // => true + * + * _.isArguments([1, 2, 3]); + * // => false */ - var contains = createIterator({ - 'args': 'collection, target', - 'init': 'false', - 'inLoop': 'if (collection[index] === target) return true' - }); + function isArguments(value) { + return toString.call(value) == argsClass; + } + // fallback for browsers that can't detect `arguments` objects by [[Class]] + if (noArgsClass) { + isArguments = function(value) { + return !!(value && hasOwnProperty.call(value, 'callee')); + }; + } /** - * Checks if the `callback` returns a truthy value for **all** elements of a - * `collection`. The `callback` is bound to `thisArg` and invoked with 3 - * arguments; for arrays they are (value, index, array) and for objects they - * are (value, key, object). + * Checks if `value` is an array. * * @static * @memberOf _ - * @alias all - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Boolean} Returns `true` if all values pass the callback check, else `false`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is an array, else `false`. * @example * - * _.every([true, 1, null, 'yes'], Boolean); + * (function() { return _.isArray(arguments); })(); * // => false + * + * _.isArray([1, 2, 3]); + * // => true */ - var every = createIterator(baseIteratorOptions, everyIteratorOptions); + var isArray = nativeIsArray || function(value) { + return toString.call(value) == arrayClass; + }; /** - * Examines each value in a `collection`, returning an array of all values the - * `callback` returns truthy for. The `callback` is bound to `thisArg` and - * invoked with 3 arguments; for arrays they are (value, index, array) and for - * objects they are (value, key, object). + * Checks if `value` is a function. * * @static * @memberOf _ - * @alias select - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of values that passed callback check. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a function, else `false`. * @example * - * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); - * // => [2, 4, 6] + * _.isFunction(''.concat); + * // => true */ - var filter = createIterator(baseIteratorOptions, filterIteratorOptions); + function isFunction(value) { + return typeof value == 'function'; + } + // fallback for older versions of Chrome and Safari + if (isFunction(/x/)) { + isFunction = function(value) { + return toString.call(value) == funcClass; + }; + } /** - * Examines each value in a `collection`, returning the first one the `callback` - * returns truthy for. The function returns as soon as it finds an acceptable - * value, and does not iterate over the entire `collection`. The `callback` is - * bound to `thisArg` and invoked with 3 arguments; for arrays they are - * (value, index, array) and for objects they are (value, key, object). - * - * @static - * @memberOf _ - * @alias detect - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the value that passed the callback check, else `undefined`. - * @example + * 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. * - * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); - * // => 2 + * @private + * @param {Mixed} value The value to check. + * @param {Boolean} [skipArgsCheck=false] Internally used to skip checks for + * `arguments` objects. + * @returns {Boolean} Returns `true` if the `value` is a plain `Object` object, + * else `false`. */ - var find = createIterator(baseIteratorOptions, forEachIteratorOptions, { - 'init': '', - 'inLoop': 'if (callback(collection[index], index, collection)) return collection[index]' - }); + function isPlainObject(value, skipArgsCheck) { + return value + ? value == ObjectProto || (value.__proto__ == ObjectProto && (skipArgsCheck || !isArguments(value))) + : false; + } + // fallback for IE + if (!isPlainObject(objectTypes)) { + isPlainObject = function(value, skipArgsCheck) { + // avoid non-objects and false positives for `arguments` objects + var result = false; + if (!(value && typeof value == 'object') || (!skipArgsCheck && isArguments(value))) { + return result; + } + // IE < 9 presents DOM nodes as `Object` objects except they have `toString` + // methods that are `typeof` "string" and still can coerce nodes to strings. + // Also check that the constructor is `Object` (i.e. `Object instanceof Object`) + var ctor = value.constructor; + if ((!noNodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) && + (!isFunction(ctor) || ctor instanceof ctor)) { + // IE < 9 iterates inherited properties before own properties. If the first + // iterated property is an object's own property then there are no inherited + // enumerable properties. + if (iteratesOwnLast) { + forIn(value, function(objValue, objKey) { + result = !hasOwnProperty.call(value, objKey); + return false; + }); + return result === 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(objValue, objKey) { + result = objKey; + }); + return result === false || hasOwnProperty.call(value, result); + } + return result; + }; + } /** - * Iterates over a `collection`, executing the `callback` for each value in the - * `collection`. The `callback` is bound to `thisArg` and invoked with 3 - * arguments; for arrays they are (value, index, array) and for objects they - * are (value, key, object). - * - * @static - * @memberOf _ - * @alias each - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array|Object} Returns the `collection`. - * @example - * - * _([1, 2, 3]).forEach(alert).join(','); - * // => alerts each number and returns '1,2,3' + * A shim implementation of `Object.keys` that produces an array of the given + * object's own enumerable property names. * - * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert); - * // => alerts each number (order is not guaranteed) + * @private + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names. */ - var forEach = createIterator(baseIteratorOptions, forEachIteratorOptions); + var shimKeys = createIterator({ + 'args': 'object', + 'init': '[]', + 'inLoop': 'result.push(index)' + }); + + /*--------------------------------------------------------------------------*/ /** - * Splits `collection` into sets, grouped by the result of running each value - * through `callback`. The `callback` is bound to `thisArg` and invoked with - * 3 arguments; for arrays they are (value, index, array) and for objects they - * are (value, key, object). The `callback` argument may also be the name of a - * property to group by. + * Creates a clone of `value`. If `deep` is `true`, all nested objects will + * also be cloned otherwise they will be assigned by reference. If a value has + * a `clone` method it will be used to perform the clone. Functions, DOM nodes, + * `arguments` objects, and objects created by constructors other than `Object` + * are **not** cloned unless they have a custom `clone` method. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function|String} callback The function called per iteration or - * property name to group by. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Object} Returns an object of grouped values. + * @category Objects + * @param {Mixed} value The value to clone. + * @param {Boolean} deep A flag to indicate a deep clone. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `deep`. + * @param {Array} [stack=[]] Internally used to keep track of traversed objects + * to avoid circular references. + * @param {Object} thorough Internally used to indicate whether or not to perform + * a more thorough clone of non-object values. + * @returns {Mixed} Returns the cloned `value`. * @example * - * _.groupBy([1.3, 2.1, 2.4], function(num) { return Math.floor(num); }); - * // => { '1': [1.3], '2': [2.1, 2.4] } + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 }, + * { 'name': 'curly', 'age': 60 } + * ]; + * + * _.clone({ 'name': 'moe' }); + * // => { 'name': 'moe' } * - * _.groupBy([1.3, 2.1, 2.4], function(num) { return this.floor(num); }, Math); - * // => { '1': [1.3], '2': [2.1, 2.4] } + * var shallow = _.clone(stooges); + * shallow[0] === stooges[0]; + * // => true * - * _.groupBy(['one', 'two', 'three'], 'length'); - * // => { '3': ['one', 'two'], '5': ['three'] } + * var deep = _.clone(stooges, true); + * shallow[0] === stooges[0]; + * // => false */ - var groupBy = createIterator(baseIteratorOptions, { - 'init': '{}', - 'top': - 'var prop, isFunc = typeof callback == \'function\';\n' + - 'if (isFunc && thisArg) callback = iteratorBind(callback, thisArg)', - 'inLoop': - 'prop = isFunc\n' + - ' ? callback(collection[index], index, collection)\n' + - ' : collection[index][callback];\n' + - '(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(collection[index])' - }); + function clone(value, deep, guard, stack, thorough) { + if (value == null) { + return value; + } + if (guard) { + deep = false; + } + // avoid slower checks on primitives + thorough || (thorough = { 'value': null }); + if (thorough.value == null) { + // primitives passed from iframes use the primary document's native prototypes + thorough.value = !!(BoolProto.clone || NumberProto.clone || StringProto.clone); + } + // use custom `clone` method if available + var isObj = objectTypes[typeof value]; + if ((isObj || thorough.value) && value.clone && isFunction(value.clone)) { + thorough.value = null; + return value.clone(deep); + } + // inspect [[Class]] + if (isObj) { + // don't clone `arguments` objects, functions, or non-object Objects + var className = toString.call(value); + if (!cloneableClasses[className] || (noArgsClass && isArguments(value))) { + return value; + } + var isArr = className == arrayClass; + isObj = isArr || (className == objectClass ? isPlainObject(value, true) : isObj); + } + // shallow clone + if (!isObj || !deep) { + // don't clone functions + return isObj + ? (isArr ? slice.call(value) : extend({}, value)) + : value; + } + + var ctor = value.constructor; + switch (className) { + case boolClass: + return new ctor(value == true); + + case dateClass: + return new ctor(+value); + + case numberClass: + case stringClass: + return new ctor(value); + + case regexpClass: + return ctor(value.source, reFlags.exec(value)); + } + + // check for circular references and return corresponding clone + stack || (stack = []); + var length = stack.length; + while (length--) { + if (stack[length].source == value) { + return stack[length].value; + } + } + + // init cloned object + length = value.length; + var result = isArr ? ctor(length) : {}; + + // add current clone and original source value to the stack of traversed objects + stack.push({ 'value': result, 'source': value }); + + // recursively populate clone (susceptible to call stack limits) + if (isArr) { + var index = -1; + while (++index < length) { + result[index] = clone(value[index], deep, null, stack, thorough); + } + } else { + forOwn(value, function(objValue, key) { + result[key] = clone(objValue, deep, null, stack, thorough); + }); + } + return result; + } /** - * Invokes the method named by `methodName` on each element in the `collection`. - * 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`. + * Assigns enumerable properties of the default object(s) to the `destination` + * object for all `destination` properties that resolve to `null`/`undefined`. + * Once a property is set, additional defaults of the same property will be + * ignored. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} 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 values returned from each invoked method. + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [default1, default2, ...] The default objects. + * @returns {Object} Returns the destination object. * @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']] + * var iceCream = { 'flavor': 'chocolate' }; + * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'rainbow' }); + * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' } */ - var invoke = createIterator(mapIteratorOptions, { - 'args': 'collection, methodName', - 'top': - 'var args = slice.call(arguments, 2),\n' + - ' isFunc = typeof methodName == \'function\'', - 'inLoop': { - 'array': 'result[index] = (isFunc ? methodName : collection[index][methodName]).apply(collection[index], args)', - 'object': 'result.push((isFunc ? methodName : collection[index][methodName]).apply(collection[index], args))' - } + var defaults = createIterator(extendIteratorOptions, { + 'inLoop': 'if (result[index] == null) ' + extendIteratorOptions.inLoop }); /** - * Produces a new array of values by mapping each element in the `collection` - * through a transformation `callback`. The `callback` is bound to `thisArg` - * and invoked with 3 arguments; for arrays they are (value, index, array) - * and for objects they are (value, key, 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 `callback` is passed, it will be executed for each property + * in the `object`, dropping the properties `callback` returns truthy for. The + * `callback` is bound to `thisArg` and invoked with 3 arguments; (value, key, object). * * @static * @memberOf _ - * @alias collect - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. + * @alias omit + * @category Objects + * @param {Object} object The source object. + * @param {Function|String} callback|[prop1, prop2, ...] The properties to drop + * or the function called per iteration. * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of values returned by the callback. + * @returns {Object} Returns an object without the dropped properties. * @example * - * _.map([1, 2, 3], function(num) { return num * 3; }); - * // => [3, 6, 9] + * _.drop({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'userid'); + * // => { 'name': 'moe', 'age': 40 } * - * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; }); - * // => [3, 6, 9] (order is not guaranteed) + * _.drop({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) { + * return key.charAt(0) == '_'; + * }); + * // => { 'name': 'moe' } */ - var map = createIterator(baseIteratorOptions, mapIteratorOptions); + var drop = createIterator(dropIteratorOptions); /** - * Retrieves the value of a specified property from all elements in - * the `collection`. + * Assigns enumerable properties of the source object(s) to the `destination` + * object. Subsequent sources will overwrite propery assignments of previous + * sources. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {String} property The property to pluck. - * @returns {Array} Returns a new array of property values. + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @returns {Object} Returns the destination object. * @example * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 }, - * { 'name': 'curly', 'age': 60 } - * ]; - * - * _.pluck(stooges, 'name'); - * // => ['moe', 'larry', 'curly'] + * _.extend({ 'name': 'moe' }, { 'age': 40 }); + * // => { 'name': 'moe', 'age': 40 } */ - var pluck = createIterator(mapIteratorOptions, { - 'args': 'collection, property', - 'inLoop': { - 'array': 'result[index] = collection[index][property]', - 'object': 'result.push(collection[index][property])' - } - }); + var extend = createIterator(extendIteratorOptions); /** - * Boils down a `collection` to a single value. The initial state of the - * reduction is `accumulator` and each successive step of it should be returned - * by the `callback`. The `callback` is bound to `thisArg` and invoked with 4 - * arguments; for arrays they are (accumulator, value, index, array) and for - * objects they are (accumulator, value, key, object). + * Iterates over `object`'s own and inherited enumerable properties, executing + * the `callback` for each property. The `callback` is bound to `thisArg` and + * invoked with 3 arguments; (value, key, object). Callbacks may exit iteration + * early by explicitly returning `false`. * * @static * @memberOf _ - * @alias foldl, inject - * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @category Objects + * @param {Object} object The object to iterate over. * @param {Function} callback The function called per iteration. - * @param {Mixed} [accumulator] Initial value of the accumulator. * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the accumulated value. + * @returns {Object} Returns `object`. * @example * - * var sum = _.reduce([1, 2, 3], function(memo, num) { return memo + num; }); - * // => 6 + * 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 reduce = createIterator({ - 'args': 'collection, callback, accumulator, thisArg', - 'init': 'accumulator', - 'top': - 'var noaccum = arguments.length < 3;\n' + - 'if (thisArg) callback = iteratorBind(callback, thisArg)', - 'beforeLoop': { - 'array': 'if (noaccum) result = collection[++index]' - }, - 'inLoop': { - 'array': - 'result = callback(result, collection[index], index, collection)', - 'object': - 'result = noaccum\n' + - ' ? (noaccum = false, collection[index])\n' + - ' : callback(result, collection[index], index, collection)' - } + var forIn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions, { + 'useHas': false }); /** - * The right-associative version of `_.reduce`. + * Iterates over `object`'s own enumerable properties, executing the `callback` + * for each property. The `callback` is bound to `thisArg` and invoked with 3 + * arguments; (value, key, object). Callbacks may exit iteration early by + * explicitly returning `false`. * * @static * @memberOf _ - * @alias foldr - * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @category Objects + * @param {Object} object The object to iterate over. * @param {Function} callback The function called per iteration. - * @param {Mixed} [accumulator] Initial value of the accumulator. * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the accumulated value. + * @returns {Object} Returns `object`. * @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] + * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { + * alert(key); + * }); + * // => alerts '0', '1', and 'length' (order is not guaranteed) */ - function reduceRight(collection, callback, accumulator, thisArg) { - if (!collection) { - return accumulator; - } - - var length = collection.length, - noaccum = arguments.length < 3; - - if(thisArg) { - callback = iteratorBind(callback, thisArg); - } - if (length === length >>> 0) { - if (length && noaccum) { - accumulator = collection[--length]; - } - while (length--) { - accumulator = callback(accumulator, collection[length], length, collection); - } - return accumulator; - } - - var prop, - props = keys(collection); - - length = props.length; - if (length && noaccum) { - accumulator = collection[props[--length]]; - } - while (length--) { - prop = props[length]; - accumulator = callback(accumulator, collection[prop], prop, collection); - } - return accumulator; - } + var forOwn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions); /** - * The opposite of `_.filter`, this method returns the values of a `collection` - * that `callback` does **not** return truthy for. + * Creates a sorted array of all enumerable properties, own and inherited, + * of `object` that have function values. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of values that did **not** pass the callback check. + * @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 * - * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); - * // => [1, 3, 5] + * _.functions(_); + * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] */ - var reject = createIterator(baseIteratorOptions, filterIteratorOptions, { - 'inLoop': '!' + filterIteratorOptions.inLoop + var functions = createIterator({ + 'useHas': false, + 'args': 'object', + 'init': '[]', + 'inLoop': 'if (isFunction(value)) result.push(index)', + 'bottom': 'result.sort()' }); /** - * 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 3 arguments; for arrays they are - * (value, index, array) and for objects they are (value, key, object). + * Checks if the specified object `property` exists and is a direct property, + * instead of an inherited property. * * @static * @memberOf _ - * @alias any - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Boolean} Returns `true` if any value passes the callback check, else `false`. + * @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 * - * _.some([null, 0, 'yes', false]); + * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); * // => true */ - var some = createIterator(baseIteratorOptions, everyIteratorOptions, { - 'init': 'false', - 'inLoop': everyIteratorOptions.inLoop.replace('!', '') - }); - + function has(object, property) { + return object ? hasOwnProperty.call(object, property) : false; + } /** - * Produces a new sorted array, ranked in ascending order by the results of - * running each element of `collection` through a transformation `callback`. - * The `callback` is bound to `thisArg` and invoked with 3 arguments; - * for arrays they are (value, index, array) and for objects they are - * (value, key, object). The `callback` argument may also be the name of a - * property to sort by (e.g. 'length'). + * Checks if `value` is a boolean (`true` or `false`) value. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to iterate over. - * @param {Function|String} callback The function called per iteration or - * property name to sort by. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a new array of sorted values. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a boolean value, else `false`. * @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] - * - * _.sortBy(['larry', 'brendan', 'moe'], 'length'); - * // => ['moe', 'larry', 'brendan'] + * _.isBoolean(null); + * // => false */ - function sortBy(collection, callback, thisArg) { - if (typeof callback == 'string') { - var prop = callback; - callback = function(collection) { return collection[prop]; }; - } else if (thisArg) { - callback = iteratorBind(callback, thisArg); - } - var result = toSortable(collection, callback).sort(compareAscending), - length = result.length; - - while (length--) { - result[length] = result[length].value; - } - return result; + function isBoolean(value) { + return value === true || value === false || toString.call(value) == boolClass; } /** - * Converts the `collection`, into an array. Useful for converting the - * `arguments` object. + * Checks if `value` is a date. * * @static * @memberOf _ - * @category Collections - * @param {Array|Object} collection The collection to convert. - * @returns {Array} Returns the new converted array. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a date, else `false`. * @example * - * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4); - * // => [2, 3, 4] + * _.isDate(new Date); + * // => true */ - function toArray(collection) { - if (!collection) { - return []; - } - if (collection.toArray && toString.call(collection.toArray) == funcClass) { - return collection.toArray(); - } - var length = collection.length; - if (length === length >>> 0) { - return slice.call(collection); - } - return values(collection); + function isDate(value) { + return toString.call(value) == dateClass; } - /*--------------------------------------------------------------------------*/ - /** - * Produces a new array with all falsey values of `array` removed. The values - * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey. + * Checks if `value` is a DOM element. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to compact. - * @returns {Array} Returns a new filtered array. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a DOM element, else `false`. * @example * - * _.compact([0, 1, false, 2, '', 3]); - * // => [1, 2, 3] + * _.isElement(document.body); + * // => true */ - function compact(array) { - var result = []; - if (!array) { - return result; - } - var index = -1, - length = array.length; + function isElement(value) { + return value ? value.nodeType === 1 : false; + } - while (++index < length) { - if (array[index]) { - result.push(array[index]); - } + /** + * 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 + */ + var isEmpty = createIterator({ + 'args': 'value', + 'init': 'true', + 'top': + 'var className = toString.call(value),\n' + + ' length = value.length;\n' + + 'if (arrayLikeClasses[className]' + + (noArgsClass ? ' || isArguments(value)' : '') + ' ||\n' + + ' (className == objectClass && length > -1 && length === length >>> 0 &&\n' + + ' isFunction(value.splice))' + + ') return !length', + 'inLoop': { + 'object': 'return false' } - return result; - } + }); /** - * Produces a new array of `array` values not present in the other arrays - * using strict equality for comparisons, i.e. `===`. + * Performs a deep comparison between two values to determine if they are + * equivalent to each other. If a value has an `isEqual` method it will be + * used to perform the comparison. * * @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` values not present in the - * other arrays. + * @category Objects + * @param {Mixed} a The value to compare. + * @param {Mixed} b The other value to compare. + * @param {Array} [stack=[]] Internally used to keep track of traversed objects + * to avoid circular references. + * @param {Object} thorough Internally used to indicate whether or not to perform + * a more thorough comparison of non-object values. + * @returns {Boolean} Returns `true` if the values are equvalent, else `false`. * @example * - * _.difference([1, 2, 3, 4, 5], [5, 2, 10]); - * // => [1, 3, 4] + * var moe = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; + * var clone = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; + * + * moe == clone; + * // => false + * + * _.isEqual(moe, clone); + * // => true */ - function difference(array) { - var result = []; - if (!array) { - return result; + function isEqual(a, b, stack, thorough) { + // a strict comparison is necessary because `null == undefined` + if (a == null || b == null) { + return a === b; + } + // avoid slower checks on non-objects + thorough || (thorough = { 'value': null }); + if (thorough.value == null) { + // primitives passed from iframes use the primary document's native prototypes + thorough.value = !!(BoolProto.isEqual || NumberProto.isEqual || StringProto.isEqual); + } + if (objectTypes[typeof a] || objectTypes[typeof b] || thorough.value) { + // unwrap any LoDash wrapped values + if (a._chain) { + a = a._wrapped; + } + if (b._chain) { + b = b._wrapped; + } + // use custom `isEqual` method if available + if (a.isEqual && isFunction(a.isEqual)) { + thorough.value = null; + return a.isEqual(b); + } + if (b.isEqual && isFunction(b.isEqual)) { + thorough.value = null; + return b.isEqual(a); + } + } + // exit early for identical values + if (a === b) { + // treat `+0` vs. `-0` as not equal + return a !== 0 || (1 / a == 1 / b); + } + // compare [[Class]] names + var className = toString.call(a); + if (className != toString.call(b)) { + 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 == b + ''; + } + // exit early, in older browsers, if `a` is array-like but not `b` + var isArr = arrayLikeClasses[className]; + if (noArgsClass && !isArr && (isArr = isArguments(a)) && !isArguments(b)) { + return false; + } + // exit for functions and DOM nodes + if (!isArr && (className != objectClass || (noNodeClass && ( + (typeof a.toString != 'function' && typeof (a + '') == 'string') || + (typeof b.toString != 'function' && typeof (b + '') == 'string'))))) { + 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) + stack || (stack = []); + var length = stack.length; + while (length--) { + if (stack[length] == a) { + return true; + } } + var index = -1, - length = array.length, - flattened = concat.apply(result, arguments); + result = true, + size = 0; - while (++index < length) { - if (indexOf(flattened, array[index], length) < 0) { - result.push(array[index]); + // add `a` to the stack of traversed objects + stack.push(a); + + // recursively compare objects and arrays (susceptible to call stack limits) + if (isArr) { + // compare lengths to determine if a deep comparison is necessary + size = a.length; + result = size == b.length; + + if (result) { + // deep compare the contents, ignoring non-numeric properties + while (size--) { + if (!(result = isEqual(a[size], b[size], stack, thorough))) { + break; + } + } } + return result; } - return result; + + 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; + } + // deep compare objects + for (var prop in a) { + if (hasOwnProperty.call(a, prop)) { + // count the number of properties. + size++; + // deep compare each property value. + if (!(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack, thorough))) { + return false; + } + } + } + // ensure both objects have the same number of properties + for (prop in b) { + // The JS engine in Adobe products, like InDesign, has a bug that causes + // `!size--` to throw an error so it must be wrapped in parentheses. + // https://github.com/documentcloud/underscore/issues/355 + if (hasOwnProperty.call(b, prop) && !(size--)) { + // `size` will be `-1` if `b` has more properties than `a` + return false; + } + } + // handle JScript [[DontEnum]] bug + if (hasDontEnumBug) { + while (++index < 7) { + prop = shadowed[index]; + if (hasOwnProperty.call(a, prop) && + !(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack, thorough))) { + return false; + } + } + } + return true; } /** - * Gets the first value of the `array`. Pass `n` to return the first `n` values - * of the `array`. + * Checks if `value` is a finite number. + * + * Note: This is not the same as native `isFinite`, which will return true for + * booleans and other values. See http://es5.github.com/#x15.1.2.5. * + * @deprecated * @static * @memberOf _ - * @alias head, take - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Mixed} Returns the first value or an array of the first `n` values - * of `array`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a finite number, else `false`. * @example * - * _.first([5, 4, 3, 2, 1]); - * // => 5 + * _.isFinite(-101); + * // => true + * + * _.isFinite('10'); + * // => false + * + * _.isFinite(Infinity); + * // => false */ - function first(array, n, guard) { - if (array) { - return (n == null || guard) ? array[0] : slice.call(array, 0, n); - } + function isFinite(value) { + return nativeIsFinite(value) && toString.call(value) == numberClass; } /** - * Flattens a nested array (the nesting can be to any depth). If `shallow` is - * truthy, `array` will only be flattened a single level. + * Checks if `value` is the language type of Object. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to compact. - * @param {Boolean} shallow A flag to indicate only flattening a single level. - * @returns {Array} Returns a new flattened array. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is an object, else `false`. * @example * - * _.flatten([1, [2], [3, [[4]]]]); - * // => [1, 2, 3, 4]; + * _.isObject({}); + * // => true * - * _.flatten([1, [2], [3, [[4]]]], true); - * // => [1, 2, 3, [[4]]]; + * _.isObject(1); + * // => false */ - function flatten(array, shallow) { - var result = []; - if (!array) { - return result; - } - var value, - index = -1, - length = array.length; - - while (++index < length) { - value = array[index]; - if (isArray(value)) { - push.apply(result, shallow ? value : flatten(value)); - } else { - result.push(value); - } - } - return result; + 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] : false; } /** - * 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 `isSorted` will run a faster binary search. + * 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. * + * @deprecated * @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 start searching from or - * `true` to perform a binary search on a sorted `array`. - * @returns {Number} Returns the index of the matched value or `-1`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is `NaN`, else `false`. * @example * - * _.indexOf([1, 2, 3, 1, 2, 3], 2); - * // => 1 + * _.isNaN(NaN); + * // => true * - * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3); - * // => 4 + * _.isNaN(new Number(NaN)); + * // => true * - * _.indexOf([1, 1, 2, 2, 3, 3], 2, true); - * // => 2 + * isNaN(undefined); + * // => true + * + * _.isNaN(undefined); + * // => false */ - function indexOf(array, value, fromIndex) { - if (!array) { - return -1; - } - var index = -1, - length = array.length; - - if (fromIndex) { - if (typeof fromIndex == 'number') { - index = (fromIndex < 0 ? Math.max(0, length + fromIndex) : fromIndex) - 1; - } else { - index = sortedIndex(array, value); - return array[index] === value ? index : -1; - } - } - while (++index < length) { - if (array[index] === value) { - return index; - } - } - return -1; + 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 toString.call(value) == numberClass && value != +value } /** - * Gets all but the last value of `array`. Pass `n` to exclude the last `n` - * values from the result. + * Checks if `value` is `null`. * + * @deprecated * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Array} Returns all but the last value or `n` values of `array`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is `null`, else `false`. * @example * - * _.initial([3, 2, 1]); - * // => [3, 2] + * _.isNull(null); + * // => true + * + * _.isNull(undefined); + * // => false */ - function initial(array, n, guard) { - if (!array) { - return []; - } - return slice.call(array, 0, -((n == null || guard) ? 1 : n)); + function isNull(value) { + return value === null; } /** - * Computes the intersection of all the passed-in arrays. + * Checks if `value` is a number. * * @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 **all** of the arrays. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a number, else `false`. * @example * - * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); - * // => [1, 2] + * _.isNumber(8.4 * 5; + * // => true */ - function intersection(array) { - var result = []; - if (!array) { - return result; - } - var value, - index = -1, - length = array.length, - others = slice.call(arguments, 1); - - while (++index < length) { - value = array[index]; - if (indexOf(result, value) < 0 && - every(others, function(other) { return indexOf(other, value) > -1; })) { - result.push(value); - } - } - return result; + function isNumber(value) { + return toString.call(value) == numberClass; } /** - * Gets the last value of the `array`. Pass `n` to return the lasy `n` values - * of the `array`. + * Checks if `value` is a regular expression. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Mixed} Returns the last value or an array of the last `n` values - * of `array`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a regular expression, else `false`. * @example * - * _.last([3, 2, 1]); - * // => 1 + * _.isRegExp(/moe/); + * // => true */ - function last(array, n, guard) { - if (array) { - var length = array.length; - return (n == null || guard) ? array[length - 1] : slice.call(array, -n || length); - } + function isRegExp(value) { + return toString.call(value) == regexpClass; } /** - * Gets the index at which the last occurrence of `value` is found using - * strict equality for comparisons, i.e. `===`. + * Checks if `value` is a string. * * @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 start searching from. - * @returns {Number} Returns the index of the matched value or `-1`. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a string, else `false`. * @example * - * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); - * // => 4 - * - * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3); - * // => 1 + * _.isString('moe'); + * // => true */ - function lastIndexOf(array, value, fromIndex) { - if (!array) { - return -1; - } - var index = array.length; - if (fromIndex && typeof fromIndex == 'number') { - index = (fromIndex < 0 ? Math.max(0, index + fromIndex) : Math.min(fromIndex, index - 1)) + 1; - } - while (index--) { - if (array[index] === value) { - return index; - } - } - return -1; + function isString(value) { + return toString.call(value) == stringClass; } /** - * 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 3 arguments; (value, index, array). + * Checks if `value` is `undefined`. * + * @deprecated * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to iterate over. - * @param {Function} [callback] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the maximum value. + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is `undefined`, else `false`. * @example * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 }, - * { 'name': 'curly', 'age': 60 } - * ]; - * - * _.max(stooges, function(stooge) { return stooge.age; }); - * // => { 'name': 'curly', 'age': 60 }; + * _.isUndefined(void 0); + * // => true */ - function max(array, callback, thisArg) { - var computed = -Infinity, - result = computed; - - if (!array) { - return result; - } - var current, - index = -1, - length = array.length; - - if (!callback) { - while (++index < length) { - if (array[index] > result) { - result = array[index]; - } - } - return result; - } - if (thisArg) { - callback = iteratorBind(callback, thisArg); - } - while (++index < length) { - current = callback(array[index], index, array); - if (current > computed) { - computed = current; - result = array[index]; - } - } - return result; + function isUndefined(value) { + return value === undefined; } /** - * 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 3 arguments; (value, index, array). + * Creates an array composed of the own enumerable property names of `object`. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to iterate over. - * @param {Function} [callback] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Mixed} Returns the minimum value. + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names. * @example * - * _.min([10, 5, 100, 2, 1000]); - * // => 2 + * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); + * // => ['one', 'two', 'three'] (order is not guaranteed) */ - function min(array, callback, thisArg) { - var computed = Infinity, - result = computed; - - if (!array) { - return result; - } - var current, - index = -1, - length = array.length; + var keys = !nativeKeys ? shimKeys : function(object) { + var type = typeof object; - if (!callback) { - while (++index < length) { - if (array[index] < result) { - result = array[index]; - } - } - return result; - } - if (thisArg) { - callback = iteratorBind(callback, thisArg); - } - while (++index < length) { - current = callback(array[index], index, array); - if (current < computed) { - computed = current; - result = array[index]; - } + // avoid iterating over the `prototype` property + if (type == 'function' && propertyIsEnumerable.call(object, 'prototype')) { + return shimKeys(object); } - return result; - } + return object && objectTypes[type] + ? nativeKeys(object) + : []; + }; /** - * Creates an array of numbers (positive and/or negative) progressing from - * `start` up to but not including `stop`. This method is a port of Python's - * `range()` function. See http://docs.python.org/library/functions.html#range. + * Merges enumerable properties of the source object(s) into the `destination` + * object. Subsequent sources will overwrite propery assignments of previous + * sources. * * @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 descrement by. - * @returns {Array} Returns a new range array. + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @param {Object} [indicator] Internally used to indicate that the `stack` + * argument is an array of traversed objects instead of another source object. + * @param {Array} [stack=[]] Internally used to keep track of traversed objects + * to avoid circular references. + * @returns {Object} Returns the destination object. * @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] + * var stooges = [ + * { 'name': 'moe' }, + * { 'name': 'larry' } + * ]; * - * _.range(0, -10, -1); - * // => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] + * var ages = [ + * { 'age': 40 }, + * { 'age': 50 } + * ]; * - * _.range(0); - * // => [] + * _.merge(stooges, ages); + * // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] */ - function range(start, end, step) { - step || (step = 1); - if (end == null) { - end = start || 0; - start = 0; - } - // use `Array(length)` so V8 will avoid the slower "dictionary" mode - // http://www.youtube.com/watch?v=XAqIpGU8ZZk#t=16m27s - var index = -1, - length = Math.max(0, Math.ceil((end - start) / step)), - result = Array(length); - - while (++index < length) { - result[index] = start; - start += step; - } - return result; - } + var merge = createIterator(extendIteratorOptions, { + 'args': 'object, source, indicator, stack', + 'top': + 'var destValue, found, isArr, stackLength, recursive = indicator == isPlainObject;\n' + + 'if (!recursive) stack = [];\n' + + 'for (var argsIndex = 1, argsLength = recursive ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n' + + ' if (iteratee = arguments[argsIndex]) {', + 'inLoop': + 'if (value && ((isArr = isArray(value)) || isPlainObject(value))) {\n' + + ' found = false; stackLength = stack.length;\n' + + ' while (stackLength--) {\n' + + ' if (found = stack[stackLength].source == value) break\n' + + ' }\n' + + ' if (found) {\n' + + ' result[index] = stack[stackLength].value\n' + + ' } else {\n' + + ' destValue = (destValue = result[index]) && isArr\n' + + ' ? (isArray(destValue) ? destValue : [])\n' + + ' : (isPlainObject(destValue) ? destValue : {});\n' + + ' stack.push({ value: destValue, source: value });\n' + + ' result[index] = callee(destValue, value, isPlainObject, stack)\n' + + ' }\n' + + '} else if (value != null) {\n' + + ' result[index] = value\n' + + '}' + }); /** - * The opposite of `_.initial`, this method gets all but the first value of - * `array`. Pass `n` to exclude the first `n` values from the 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 3 arguments; (value, key, object). * * @static * @memberOf _ - * @alias tail - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Array} Returns all but the first value or `n` values of `array`. + * @category Objects + * @param {Object} object The source object. + * @param {Function|String} callback|[prop1, prop2, ...] The properties to pick + * or the function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Object} Returns an object composed of the picked properties. * @example * - * _.rest([3, 2, 1]); - * // => [2, 1] + * _.pick({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'name', 'age'); + * // => { 'name': 'moe', 'age': 40 } + * + * _.pick({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) { + * return key.charAt(0) != '_'; + * }); + * // => { 'name': 'moe' } */ - function rest(array, n, guard) { - if (!array) { - return []; - } - return slice.call(array, (n == null || guard) ? 1 : n); - } + var pick = createIterator(dropIteratorOptions, { + 'top': + 'if (typeof callback != \'function\') {\n' + + ' var prop,\n' + + ' props = concat.apply(ArrayProto, arguments),\n' + + ' length = props.length;\n' + + ' for (index = 1; index < length; index++) {\n' + + ' prop = props[index];\n' + + ' if (prop in object) result[prop] = object[prop]\n' + + ' }\n' + + '} else {\n' + + ' if (thisArg) callback = iteratorBind(callback, thisArg)', + 'inLoop': + 'if (callback(value, index, object)) result[index] = value', + 'bottom': '}' + }); /** - * Produces a new array of shuffled `array` values, using a version of the - * Fisher-Yates shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. + * Gets the size of `value` by returning `value.length` if `value` is an + * array, string, or `arguments` object. If `value` is an object, size is + * determined by returning the number of own enumerable properties it has. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to shuffle. - * @returns {Array} Returns a new shuffled array. + * @category Objects + * @param {Array|Object|String} value The value to inspect. + * @returns {Number} Returns `value.length` or number of own enumerable properties. * @example * - * _.shuffle([1, 2, 3, 4, 5, 6]); - * // => [4, 1, 6, 3, 5, 2] + * _.size([1, 2]); + * // => 2 + * + * _.size({ 'one': 1, 'two': 2, 'three': 3 }); + * // => 3 + * + * _.size('curly'); + * // => 5 */ - function shuffle(array) { - if (!array) { - return []; + function size(value) { + if (!value) { + return 0; } - var rand, - index = -1, - length = array.length, - result = Array(length); + var className = toString.call(value), + length = value.length; - while (++index < length) { - rand = Math.floor(Math.random() * (index + 1)); - result[index] = result[rand]; - result[rand] = array[index]; + // return `value.length` for `arguments` objects, arrays, strings, and DOM + // query collections of libraries like jQuery and MooTools + // http://code.google.com/p/fbug/source/browse/branches/firebug1.9/content/firebug/chrome/reps.js?r=12614#653 + // http://trac.webkit.org/browser/trunk/Source/WebCore/inspector/InjectedScriptSource.js?rev=125186#L609 + if (arrayLikeClasses[className] || (noArgsClass && isArguments(value)) || + (className == objectClass && length > -1 && length === length >>> 0 && isFunction(value.splice))) { + return length; } - return result; + return keys(value).length; } /** - * 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 1 argument; (value). + * Creates an array composed of the own enumerable property values of `object`. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to iterate over. - * @param {Mixed} value The value to evaluate. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Number} Returns the index at which the value should be inserted - * into `array`. + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property values. * @example * - * _.sortedIndex([20, 30, 40], 35); - * // => 2 - * - * var dict = { - * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'thirty-five': 35, 'fourty': 40 } - * }; - * - * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { - * return dict.wordToNumber[word]; - * }); - * // => 2 - * - * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { - * return this.wordToNumber[word]; - * }, dict); - * // => 2 + * _.values({ 'one': 1, 'two': 2, 'three': 3 }); + * // => [1, 2, 3] */ - function sortedIndex(array, value, callback, thisArg) { - if (!array) { - return 0; - } - var mid, - low = 0, - high = array.length; + var values = createIterator({ + 'args': 'object', + 'init': '[]', + 'inLoop': 'result.push(value)' + }); - if (callback) { - if (thisArg) { - callback = bind(callback, thisArg); - } - value = callback(value); - while (low < high) { - mid = (low + high) >>> 1; - callback(array[mid]) < value ? low = mid + 1 : high = mid; - } - } else { - while (low < high) { - mid = (low + high) >>> 1; - array[mid] < value ? low = mid + 1 : high = mid; - } - } - return low; - } + /*--------------------------------------------------------------------------*/ /** - * Computes the union of the passed-in arrays. + * Checks if a given `target` element is present in a `collection` 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. + * @alias include + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Mixed} target The value to check for. + * @returns {Boolean} Returns `true` if the `target` element is found, else `false`. * @example * - * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); - * // => [1, 2, 3, 101, 10] + * _.contains([1, 2, 3], 3); + * // => true + * + * _.contains({ 'name': 'moe', 'age': 40 }, 'moe'); + * // => true + * + * _.contains('curly', 'ur'); + * // => true */ - function union() { - var index = -1, - result = [], - flattened = concat.apply(result, arguments), - length = flattened.length; - - while (++index < length) { - if (indexOf(result, flattened[index]) < 0) { - result.push(flattened[index]); - } - } - return result; - } + var contains = createIterator({ + 'args': 'collection, target', + 'init': 'false', + 'noCharByIndex': false, + 'beforeLoop': { + 'array': 'if (toString.call(collection) == stringClass) return collection.indexOf(target) > -1' + }, + 'inLoop': 'if (value === target) return true' + }); /** - * Produces 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 value of `array` is passed through a transformation `callback` before - * uniqueness is computed. The `callback` is bound to `thisArg` and invoked - * with 3 arguments; (value, index, array). + * Creates an object composed of keys returned from running each element of + * `collection` through a `callback`. The corresponding value of each key is + * the number of times the key was returned by `callback`. The `callback` is + * bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection). + * The `callback` argument may also be the name of a property to count by (e.g. 'length'). * * @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} [callback=identity] The function called per iteration. + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|String} callback|property The function called per iteration + * or property name to count by. * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Array} Returns a duplicate-value-free array. + * @returns {Object} Returns the composed aggregate object. * @example * - * _.uniq([1, 2, 1, 3, 1]); - * // => [1, 2, 3] - * - * _.uniq([1, 1, 2, 2, 3], true); - * // => [1, 2, 3] + * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); }); + * // => { '4': 1, '6': 2 } * - * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); }); - * // => [1, 2, 3] + * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math); + * // => { '4': 1, '6': 2 } * - * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math); - * // => [1, 2, 3] + * _.countBy(['one', 'two', 'three'], 'length'); + * // => { '3': 2, '5': 1 } */ - function uniq(array, isSorted, callback, thisArg) { - var result = []; - if (!array) { - return result; - } - var computed, - index = -1, - length = array.length, - seen = []; - - // juggle arguments - if (typeof isSorted == 'function') { - thisArg = callback; - callback = isSorted; - isSorted = false; - } - if (!callback) { - callback = identity; - } else if (thisArg) { - callback = iteratorBind(callback, thisArg); - } - while (++index < length) { - computed = callback(array[index], index, array); - if (isSorted - ? !index || seen[seen.length - 1] !== computed - : indexOf(seen, computed) < 0 - ) { - seen.push(computed); - result.push(array[index]); - } - } - return result; - } + var countBy = createIterator(baseIteratorOptions, countByIteratorOptions); /** - * Produces a new array with all occurrences of the passed values removed using - * strict equality for comparisons, i.e. `===`. + * Checks if the `callback` returns a truthy value for **all** elements of a + * `collection`. The `callback` is bound to `thisArg` and invoked with 3 + * arguments; (value, index|key, collection). * * @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. + * @alias all + * @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 for the callback. + * @returns {Boolean} Returns `true` if all elements pass the callback check, else `false`. * @example * - * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); - * // => [2, 3, 4] + * _.every([true, 1, null, 'yes'], Boolean); + * // => false */ - function without(array) { - var result = []; - if (!array) { - return result; - } - var index = -1, - length = array.length; - - while (++index < length) { - if (indexOf(arguments, array[index], 1) < 0) { - result.push(array[index]); - } - } - return result; - } + var every = createIterator(baseIteratorOptions, everyIteratorOptions); /** - * Merges together the values of each of the arrays with the value at the - * corresponding position. 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. + * 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 3 arguments; (value, index|key, collection). * * @static * @memberOf _ - * @category Arrays - * @param {Array} [array1, array2, ...] Arrays to process. - * @returns {Array} Returns a new array of merged arrays. + * @alias select + * @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 for the callback. + * @returns {Array} Returns a new array of elements that passed callback check. * @example * - * _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]); - * // => [['moe', 30, true], ['larry', 40, false], ['curly', 50, false]] + * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [2, 4, 6] */ - function zip(array) { - if (!array) { - return []; - } - var index = -1, - length = max(pluck(arguments, 'length')), - result = Array(length); - - while (++index < length) { - result[index] = pluck(arguments, index); - } - return result; - } - - /*--------------------------------------------------------------------------*/ + var filter = createIterator(baseIteratorOptions, filterIteratorOptions); /** - * Creates a new function that is restricted to executing only after it is - * called `n` times. + * Examines each element in a `collection`, returning the first one the `callback` + * returns truthy for. The function returns as soon as it finds an acceptable + * element, and does not iterate over the entire `collection`. The `callback` is + * bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection). * * @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. + * @alias detect + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the element that passed the callback check, else `undefined`. * @example * - * var renderNotes = _.after(notes.length, render); - * _.forEach(notes, function(note) { - * note.asyncSave({ 'success': renderNotes }); - * }); - * // `renderNotes` is run once, after all notes have saved + * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => 2 */ - function after(n, func) { - if (n < 1) { - return func(); - } - return function() { - if (--n < 1) { - return func.apply(this, arguments); - } - }; - } + var find = createIterator(baseIteratorOptions, forEachIteratorOptions, { + 'init': '', + 'inLoop': 'if (callback(value, index, collection)) return value' + }); /** - * Creates a new 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. Lazy defined methods may be bound by passing - * the object they are bound to as `func` and the method name as `thisArg`. + * Iterates over a `collection`, executing the `callback` for each element in + * the `collection`. The `callback` is bound to `thisArg` and invoked with 3 + * arguments; (value, index|key, collection). Callbacks may exit iteration + * early by explicitly returning `false`. * * @static * @memberOf _ - * @category Functions - * @param {Function|Object} func The function to bind or the object the method belongs to. - * @param {Mixed} [thisArg] The `this` binding of `func` or the method name. - * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. - * @returns {Function} Returns the new bound function. + * @alias each + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Array|Object} Returns `collection`. * @example * - * // basic bind - * var func = function(greeting) { - * return greeting + ': ' + this.name; - * }; + * _([1, 2, 3]).forEach(alert).join(','); + * // => alerts each number and returns '1,2,3' * - * func = _.bind(func, { 'name': 'moe' }, 'hi'); - * func(); - * // => 'hi: moe' + * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert); + * // => alerts each number (order is not guaranteed) + */ + var forEach = createIterator(baseIteratorOptions, forEachIteratorOptions); + + /** + * Creates an object composed of keys returned from running each element of + * `collection` through a `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 3 arguments; (value, index|key, collection). + * The `callback` argument may also be the name of a property to count by (e.g. 'length'). * - * // lazy bind - * var object = { - * 'name': 'moe', - * 'greet': function(greeting) { - * return greeting + ': ' + this.name; - * } - * }; + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|String} callback|property The function called per iteration + * or property name to group by. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Object} Returns the composed aggregate object. + * @example * - * var func = _.bind(object, 'greet', 'hi'); - * func(); - * // => 'hi: moe' + * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); }); + * // => { '4': [4.2], '6': [6.1, 6.4] } * - * object.greet = function(greeting) { - * return greeting + ', ' + this.name + '!'; - * }; + * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math); + * // => { '4': [4.2], '6': [6.1, 6.4] } * - * func(); - * // => 'hi, moe!' + * _.groupBy(['one', 'two', 'three'], 'length'); + * // => { '3': ['one', 'two'], '5': ['three'] } */ - function bind(func, thisArg) { - var methodName, - isFunc = toString.call(func) == funcClass; - - // juggle arguments - if (!isFunc) { - methodName = thisArg; - thisArg = func; - } - // use if `Function#bind` is faster - else if (nativeBind) { - return nativeBind.call.apply(nativeBind, arguments); - } - - var partialArgs = slice.call(arguments, 2); - - function bound() { - // `Function#bind` spec - // http://es5.github.com/#x15.3.4.5 - var args = arguments, - thisBinding = thisArg; - - if (!isFunc) { - func = thisArg[methodName]; - } - if (partialArgs.length) { - args = args.length - ? concat.apply(partialArgs, args) - : partialArgs; - } - if (this instanceof bound) { - // get `func` instance if `bound` is invoked in a `new` expression - noop.prototype = func.prototype; - thisBinding = new noop; - - // mimic the constructor's `return` behavior - // http://es5.github.com/#x13.2.2 - var result = func.apply(thisBinding, args); - return objectTypes[typeof result] && result !== null - ? result - : thisBinding - } - return func.apply(thisBinding, args); - } - - return bound; - } + var groupBy = createIterator(baseIteratorOptions, countByIteratorOptions, { + 'inLoop': + 'prop = callback(value, index, collection);\n' + + '(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(value)' + }); /** - * Binds methods on `object` to `object`, overwriting the existing method. - * If no method names are provided, all the function properties of `object` - * will be bound. + * Invokes the method named by `methodName` on each element in the `collection`. + * 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 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 the `object`. + * @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 values returned from each invoked method. * @example * - * var buttonView = { - * 'label': 'lodash', - * 'onClick': function() { alert('clicked: ' + this.label); } - * }; + * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); + * // => [[1, 5, 7], [1, 2, 3]] * - * _.bindAll(buttonView); - * jQuery('#lodash_button').on('click', buttonView.onClick); - * // => When the button is clicked, `this.label` will have the correct value + * _.invoke([123, 456], String.prototype.split, ''); + * // => [['1', '2', '3'], ['4', '5', '6']] */ - function bindAll(object) { - var funcs = arguments, - index = 1; - - if (funcs.length == 1) { - index = 0; - funcs = functions(object); - } - for (var length = funcs.length; index < length; index++) { - object[funcs[index]] = bind(object[funcs[index]], object); + var invoke = createIterator(mapIteratorOptions, { + 'args': 'collection, methodName', + 'top': + 'var args = slice.call(arguments, 2),\n' + + ' isFunc = typeof methodName == \'function\'', + 'inLoop': { + 'array': + 'result[index] = (isFunc ? methodName : value[methodName]).apply(value, args)', + 'object': + 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + + '((isFunc ? methodName : value[methodName]).apply(value, args))' } - return object; - } + }); /** - * Creates a new function that is the composition of the passed functions, - * where each function consumes the return value of the function that follows. - * In math terms, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. + * Creates a new array of values by running each element in the `collection` + * through a `callback`. The `callback` is bound to `thisArg` and invoked with + * 3 arguments; (value, index|key, collection). * * @static * @memberOf _ - * @category Functions - * @param {Function} [func1, func2, ...] Functions to compose. - * @returns {Function} Returns the new composed function. + * @alias collect + * @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 for the callback. + * @returns {Array} Returns a new array of elements returned by the callback. * @example * - * var greet = function(name) { return 'hi: ' + name; }; - * var exclaim = function(statement) { return statement + '!'; }; - * var welcome = _.compose(exclaim, greet); - * welcome('moe'); - * // => 'hi: moe!' + * _.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) */ - function compose() { - var funcs = arguments; - return function() { - var args = arguments, - length = funcs.length; - - while (length--) { - args = [funcs[length].apply(this, args)]; - } - return args[0]; - }; - } + var map = createIterator(baseIteratorOptions, mapIteratorOptions); /** - * Creates a new function that will delay the execution of `func` until after - * `wait` milliseconds have elapsed since the last time it was invoked. Pass - * `true` for `immediate` to cause debounce to invoke `func` on the leading, - * instead of the trailing, edge of the `wait` timeout. Subsequent calls to - * the debounced function will return the result of the last `func` call. + * Retrieves the value of a specified property from all elements in + * the `collection`. * * @static * @memberOf _ - * @category Functions - * @param {Function} func The function to debounce. - * @param {Number} wait The number of milliseconds to delay. - * @param {Boolean} immediate A flag to indicate execution is on the leading - * edge of the timeout. - * @returns {Function} Returns the new debounced 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 lazyLayout = _.debounce(calculateLayout, 300); - * jQuery(window).on('resize', lazyLayout); + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 }, + * { 'name': 'curly', 'age': 60 } + * ]; + * + * _.pluck(stooges, 'name'); + * // => ['moe', 'larry', 'curly'] */ - function debounce(func, wait, immediate) { - var args, - result, - thisArg, - timeoutId; - - function delayed() { - timeoutId = null; - if (!immediate) { - func.apply(thisArg, args); - } + var pluck = createIterator(mapIteratorOptions, { + 'args': 'collection, property', + 'inLoop': { + 'array': 'result[index] = value[property]', + 'object': 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '(value[property])' } - - return function() { - var isImmediate = immediate && !timeoutId; - args = arguments; - thisArg = this; - - clearTimeout(timeoutId); - timeoutId = setTimeout(delayed, wait); - - if (isImmediate) { - result = func.apply(thisArg, args); - } - return result; - }; - } + }); /** - * Executes the `func` function after `wait` milliseconds. Additional arguments - * are passed to `func` when it is invoked. + * Boils down a `collection` to a single value. The initial state of the + * reduction is `accumulator` and each successive step of it should be returned + * by the `callback`. The `callback` is bound to `thisArg` and invoked with 4 + * arguments; for arrays they are (accumulator, value, index|key, collection). * * @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 `setTimeout` timeout id. + * @alias foldl, inject + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} [accumulator] Initial value of the accumulator. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the accumulated value. * @example * - * var log = _.bind(console.log, console); - * _.delay(log, 1000, 'logged later'); - * // => 'logged later' (Appears after one second.) + * var sum = _.reduce([1, 2, 3], function(memo, num) { return memo + num; }); + * // => 6 */ - function delay(func, wait) { - var args = slice.call(arguments, 2); - return setTimeout(function() { return func.apply(undefined, args); }, wait); - } + var reduce = createIterator({ + 'args': 'collection, callback, accumulator, thisArg', + 'init': 'accumulator', + 'top': + 'var noaccum = arguments.length < 3;\n' + + 'if (thisArg) callback = iteratorBind(callback, thisArg)', + 'beforeLoop': { + 'array': 'if (noaccum) result = iteratee[++index]' + }, + 'inLoop': { + 'array': + 'result = callback(result, value, index, collection)', + 'object': + 'result = noaccum\n' + + ' ? (noaccum = false, value)\n' + + ' : callback(result, value, index, collection)' + } + }); /** - * Defers executing the `func` function until the current call stack has cleared. - * Additional arguments are passed to `func` when it is invoked. + * The right-associative version of `_.reduce`. * * @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 `setTimeout` timeout id. + * @alias foldr + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} [accumulator] Initial value of the accumulator. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the accumulated value. * @example * - * _.defer(function() { alert('deferred'); }); - * // returns from the function before `alert` is called + * 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 defer(func) { - var args = slice.call(arguments, 1); - return setTimeout(function() { return func.apply(undefined, args); }, 1); + function reduceRight(collection, callback, accumulator, thisArg) { + if (!collection) { + return accumulator; + } + + var length = collection.length, + noaccum = arguments.length < 3; + + if(thisArg) { + callback = iteratorBind(callback, thisArg); + } + // Opera 10.53-10.60 JITted `length >>> 0` returns the wrong value for negative numbers + if (length > -1 && length === length >>> 0) { + var iteratee = noCharByIndex && toString.call(collection) == stringClass + ? collection.split('') + : collection; + + if (length && noaccum) { + accumulator = iteratee[--length]; + } + while (length--) { + accumulator = callback(accumulator, iteratee[length], length, collection); + } + return accumulator; + } + + var prop, + props = keys(collection); + + length = props.length; + if (length && noaccum) { + accumulator = collection[props[--length]]; + } + while (length--) { + prop = props[length]; + accumulator = callback(accumulator, collection[prop], prop, collection); + } + return accumulator; } /** - * Creates a new 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 opposite of `_.filter`, this method returns the values of a + * `collection` that `callback` does **not** return truthy for. * * @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. + * @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 for the callback. + * @returns {Array} Returns a new array of elements that did **not** pass the callback check. * @example * - * var fibonacci = _.memoize(function(n) { - * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); - * }); + * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [1, 3, 5] */ - function memoize(func, resolver) { - var cache = {}; - return function() { - var prop = resolver ? resolver.apply(this, arguments) : arguments[0]; - return hasOwnProperty.call(cache, prop) - ? cache[prop] - : (cache[prop] = func.apply(this, arguments)); - }; - } + var reject = createIterator(baseIteratorOptions, filterIteratorOptions, { + 'inLoop': '!' + filterIteratorOptions.inLoop + }); /** - * Creates a new function that is restricted to one execution. Repeat calls to - * the function will return the value of the first call. + * 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 3 arguments; (value, index|key, collection). * * @static * @memberOf _ - * @category Functions - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. + * @alias any + * @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 for the callback. + * @returns {Boolean} Returns `true` if any element passes the callback check, else `false`. * @example * - * var initialize = _.once(createApplication); - * initialize(); - * initialize(); - * // Application is only created once. + * _.some([null, 0, 'yes', false]); + * // => true */ - function once(func) { - var result, - ran = false; - - return function() { - if (ran) { - return result; - } - ran = true; - result = func.apply(this, arguments); - return result; - }; - } + var some = createIterator(baseIteratorOptions, everyIteratorOptions, { + 'init': 'false', + 'inLoop': everyIteratorOptions.inLoop.replace('!', '') + }); /** - * Creates a new function that, when called, invokes `func` with any additional - * `partial` arguments prepended to those passed to the partially applied - * function. This method is similar `bind`, except it does **not** alter the - * `this` binding. + * Creates a new array, stable sorted in ascending order by the results of + * running each element of `collection` through a `callback`. The `callback` + * is bound to `thisArg` and invoked with 3 arguments; (value, index|key, collection). + * The `callback` argument may also be the name of a property to sort by (e.g. 'length'). * * @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. + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|String} callback|property The function called per iteration + * or property name to sort by. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Array} Returns a new array of sorted elements. * @example * - * var greet = function(greeting, name) { return greeting + ': ' + name; }; - * var hi = _.partial(greet, 'hi'); - * hi('moe'); - * // => 'hi: moe' + * _.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] + * + * _.sortBy(['larry', 'brendan', 'moe'], 'length'); + * // => ['moe', 'larry', 'brendan'] */ - function partial(func) { - var args = slice.call(arguments, 1), - argsLength = args.length; - - return function() { - var result, - others = arguments; - - if (others.length) { - args.length = argsLength; - push.apply(args, others); - } - result = args.length == 1 ? func.call(this, args[0]) : func.apply(this, args); - args.length = argsLength; - return result; - }; - } + var sortBy = createIterator(baseIteratorOptions, countByIteratorOptions, mapIteratorOptions, { + 'inLoop': { + 'array': + 'result[index] = {\n' + + ' criteria: callback(value, index, collection),\n' + + ' index: index,\n' + + ' value: value\n' + + '}', + 'object': + 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '({\n' + + ' criteria: callback(value, index, collection),\n' + + ' index: index,\n' + + ' value: value\n' + + '})' + }, + 'bottom': + 'result.sort(compareAscending);\n' + + 'length = result.length;\n' + + 'while (length--) {\n' + + ' result[length] = result[length].value\n' + + '}' + }); /** - * Creates a new function that, when executed, will only call the `func` - * function at most once per every `wait` milliseconds. If the throttled - * function is invoked more than once during the `wait` timeout, `func` will - * also be called on the trailing edge of the timeout. Subsequent calls to the - * throttled function will return the result of the last `func` call. + * Converts the `collection`, to an array. Useful for converting the + * `arguments` object. * * @static * @memberOf _ - * @category Functions - * @param {Function} func The function to throttle. - * @param {Number} wait The number of milliseconds to throttle executions to. - * @returns {Function} Returns the new throttled function. + * @category Collections + * @param {Array|Object|String} collection The collection to convert. + * @returns {Array} Returns the new converted array. * @example * - * var throttled = _.throttle(updatePosition, 100); - * jQuery(window).on('scroll', throttled); + * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4); + * // => [2, 3, 4] */ - function throttle(func, wait) { - var args, - result, - thisArg, - timeoutId, - lastCalled = 0; - - function trailingCall() { - lastCalled = new Date; - timeoutId = null; - func.apply(thisArg, args); + function toArray(collection) { + if (!collection) { + return []; } + if (collection.toArray && isFunction(collection.toArray)) { + return collection.toArray(); + } + var length = collection.length; + if (length > -1 && length === length >>> 0) { + return (noArraySliceOnStrings ? toString.call(collection) == stringClass : typeof collection == 'string') + ? collection.split('') + : slice.call(collection); + } + return values(collection); + } - return function() { - var now = new Date, - remain = wait - (now - lastCalled); - - args = arguments; - thisArg = this; + /** + * Examines each element in a `collection`, returning an array of all elements + * that contain the given `properties`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Object} properties The object of properties/values to filter by. + * @returns {Array} Returns a new array of elements that contain the given `properties`. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 }, + * { 'name': 'curly', 'age': 60 } + * ]; + * + * _.where(stooges, { 'age': 40 }); + * // => [{ 'name': 'moe', 'age': 40 }] + */ + var where = createIterator(filterIteratorOptions, { + 'args': 'collection, properties', + 'top': + 'var props = [];\n' + + 'forIn(properties, function(value, prop) { props.push(prop) });\n' + + 'var propsLength = props.length', + 'inLoop': + 'for (var prop, pass = true, propIndex = 0; propIndex < propsLength; propIndex++) {\n' + + ' prop = props[propIndex];\n' + + ' if (!(pass = value[prop] === properties[prop])) break\n' + + '}\n' + + 'pass && result.push(value)' + }); - if (remain <= 0) { - lastCalled = now; - result = func.apply(thisArg, args); - } - else if (!timeoutId) { - timeoutId = setTimeout(trailingCall, remain); - } - return result; - }; - } + /*--------------------------------------------------------------------------*/ /** - * Create a new function that passes the `func` function to the `wrapper` - * function as its first argument. Additional arguments are appended to those - * passed to the `wrapper` function. + * Creates a new array with all falsey values of `array` removed. The values + * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey. * * @static * @memberOf _ - * @category Functions - * @param {Function} func The function to wrap. - * @param {Function} wrapper The wrapper function. - * @param {Mixed} [arg1, arg2, ...] Arguments to append to those passed to the wrapper. - * @returns {Function} Returns the new function. + * @category Arrays + * @param {Array} array The array to compact. + * @returns {Array} Returns a new filtered array. * @example * - * var hello = function(name) { return 'hello: ' + name; }; - * hello = _.wrap(hello, function(func) { - * return 'before, ' + func('moe') + ', after'; - * }); - * hello(); - * // => 'before, hello: moe, after' + * _.compact([0, 1, false, 2, '', 3]); + * // => [1, 2, 3] */ - function wrap(func, wrapper) { - return function() { - var args = [func]; - if (arguments.length) { - push.apply(args, arguments); + function compact(array) { + var result = []; + if (!array) { + return result; + } + var index = -1, + length = array.length; + + while (++index < length) { + if (array[index]) { + result.push(array[index]); } - return wrapper.apply(this, args); - }; + } + return result; } - /*--------------------------------------------------------------------------*/ - /** - * Create a shallow clone of the `value`. Any nested objects or arrays will be - * assigned by reference and not cloned. + * Creates a new array of `array` elements not present in the other arrays + * using strict equality for comparisons, i.e. `===`. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to clone. - * @returns {Mixed} Returns the cloned `value`. + * @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 * - * _.clone({ 'name': 'moe' }); - * // => { 'name': 'moe' }; + * _.difference([1, 2, 3, 4, 5], [5, 2, 10]); + * // => [1, 3, 4] */ - function clone(value) { - return objectTypes[typeof value] && value !== null - ? (isArray(value) ? value.slice() : extend({}, value)) - : value; + function difference(array) { + var result = []; + if (!array) { + return result; + } + var index = -1, + length = array.length, + flattened = concat.apply(result, arguments), + contains = cachedContains(flattened, length); + + while (++index < length) { + if (!contains(array[index])) { + result.push(array[index]); + } + } + return result; } /** - * Assigns missing properties on `object` with default values from the defaults - * objects. Once a property is set, additional defaults of the same property - * will be ignored. + * Gets the first element of the `array`. Pass `n` to return the first `n` + * elements of the `array`. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to populate. - * @param {Object} [defaults1, defaults2, ...] The defaults objects to apply to `object`. - * @returns {Object} Returns `object`. + * @alias head, take + * @category Arrays + * @param {Array} array The array to query. + * @param {Number} [n] The number of elements to return. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `n`. + * @returns {Mixed} Returns the first element or an array of the first `n` + * elements of `array`. * @example * - * var iceCream = { 'flavor': 'chocolate' }; - * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'rainbow' }); - * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' } + * _.first([5, 4, 3, 2, 1]); + * // => 5 */ - var defaults = createIterator(extendIteratorOptions, { - 'inLoop': 'if (object[index] == null)' + extendIteratorOptions.inLoop - }); + function first(array, n, guard) { + if (array) { + return (n == null || guard) ? array[0] : slice.call(array, 0, n); + } + } /** - * Copies enumerable properties from the source objects to the `destination` object. - * Subsequent sources will overwrite propery assignments of previous sources. + * Flattens a nested array (the nesting can be to any depth). If `shallow` is + * truthy, `array` will only be flattened a single level. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The destination object. - * @param {Object} [source1, source2, ...] The source objects. - * @returns {Object} Returns the destination object. + * @category Arrays + * @param {Array} array The array to compact. + * @param {Boolean} shallow A flag to indicate only flattening a single level. + * @returns {Array} Returns a new flattened array. * @example * - * _.extend({ 'name': 'moe' }, { 'age': 40 }); - * // => { 'name': 'moe', 'age': 40 } + * _.flatten([1, [2], [3, [[4]]]]); + * // => [1, 2, 3, 4]; + * + * _.flatten([1, [2], [3, [[4]]]], true); + * // => [1, 2, 3, [[4]]]; */ - var extend = createIterator(extendIteratorOptions); + function flatten(array, shallow) { + var result = []; + if (!array) { + return result; + } + var value, + index = -1, + length = array.length; + + while (++index < length) { + value = array[index]; + if (isArray(value)) { + push.apply(result, shallow ? value : flatten(value)); + } else { + result.push(value); + } + } + 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 3 arguments; (value, key, object). + * 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 `isSorted` will run a faster binary search. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Object} Returns the `object`. + * @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 start searching from or + * `true` to perform a binary search on a sorted `array`. + * @returns {Number} Returns the index of the matched value or `-1`. * @example * - * function Dog(name) { - * this.name = name; - * } + * _.indexOf([1, 2, 3, 1, 2, 3], 2); + * // => 1 * - * Dog.prototype.bark = function() { - * alert('Woof, woof!'); - * }; + * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3); + * // => 4 * - * _.forIn(new Dog('Dagny'), function(value, key) { - * alert(key); - * }); - * // => alerts 'name' and 'bark' (order is not guaranteed) + * _.indexOf([1, 1, 2, 2, 3, 3], 2, true); + * // => 2 */ - var forIn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions, { - 'useHas': false - }); + function indexOf(array, value, fromIndex) { + if (!array) { + return -1; + } + var index = -1, + length = array.length; + + if (fromIndex) { + if (typeof fromIndex == 'number') { + index = (fromIndex < 0 ? Math.max(0, length + fromIndex) : fromIndex) - 1; + } else { + index = sortedIndex(array, value); + return array[index] === value ? index : -1; + } + } + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; + } /** - * Iterates over `object`'s own enumerable properties, executing the `callback` - * for each property. The `callback` is bound to `thisArg` and invoked with 3 - * arguments; (value, key, object). + * Gets all but the last element of `array`. Pass `n` to exclude the last `n` + * elements from the result. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to iterate over. - * @param {Function} callback The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Object} Returns the `object`. + * @category Arrays + * @param {Array} array The array to query. + * @param {Number} [n] The number of elements to return. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `n`. + * @returns {Array} Returns all but the last element or `n` elements of `array`. * @example * - * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { - * alert(key); - * }); - * // => alerts '0', '1', and 'length' (order is not guaranteed) + * _.initial([3, 2, 1]); + * // => [3, 2] */ - var forOwn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions); + function initial(array, n, guard) { + if (!array) { + return []; + } + return slice.call(array, 0, -((n == null || guard) ? 1 : n)); + } /** - * Produces a sorted array of the enumerable properties, own and inherited, - * of `object` that have function values. + * Computes the intersection of all the passed-in arrays using strict equality + * for comparisons, i.e. `===`. * * @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. + * @category Arrays + * @param {Array} [array1, array2, ...] Arrays to process. + * @returns {Array} Returns a new array of unique elements, in order, that are + * present in **all** of the arrays. * @example * - * _.functions(_); - * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] + * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); + * // => [1, 2] */ - var functions = createIterator({ - 'args': 'object', - 'init': '[]', - 'useHas': false, - 'inLoop': 'if (toString.call(object[index]) == funcClass) result.push(index)', - 'bottom': 'result.sort()' - }); + function intersection(array) { + var result = []; + if (!array) { + return result; + } + var value, + argsLength = arguments.length, + cache = [], + index = -1, + length = array.length; + + array: while (++index < length) { + value = array[index]; + if (indexOf(result, value) < 0) { + for (var argsIndex = 1; argsIndex < argsLength; argsIndex++) { + if (!(cache[argsIndex] || (cache[argsIndex] = cachedContains(arguments[argsIndex])))(value)) { + continue array; + } + } + result.push(value); + } + } + return result; + } /** - * Checks if the specified object `property` exists and is a direct property, - * instead of an inherited property. + * Gets the last element of the `array`. Pass `n` to return the lasy `n` + * elementsvof the `array`. * * @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`. + * @category Arrays + * @param {Array} array The array to query. + * @param {Number} [n] The number of elements to return. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `n`. + * @returns {Mixed} Returns the last element or an array of the last `n` + * elements of `array`. * @example * - * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); - * // => true + * _.last([3, 2, 1]); + * // => 1 */ - function has(object, property) { - return hasOwnProperty.call(object, property); + function last(array, n, guard) { + if (array) { + var length = array.length; + return (n == null || guard) ? array[length - 1] : slice.call(array, -n || length); + } } /** - * Checks if `value` is an `arguments` object. + * Gets the index at which the last occurrence of `value` is found using + * strict equality for comparisons, i.e. `===`. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is an `arguments` object, else `false`. + * @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 start searching from. + * @returns {Number} Returns the index of the matched value or `-1`. * @example * - * (function() { return _.isArguments(arguments); })(1, 2, 3); - * // => true + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); + * // => 4 * - * _.isArguments([1, 2, 3]); - * // => false + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3); + * // => 1 */ - var isArguments = function(value) { - return toString.call(value) == '[object Arguments]'; - }; - // fallback for browser like IE < 9 which detect `arguments` as `[object Object]` - if (!isArguments(arguments)) { - isArguments = function(value) { - return !!(value && hasOwnProperty.call(value, 'callee')); - }; + function lastIndexOf(array, value, fromIndex) { + if (!array) { + return -1; + } + var index = array.length; + if (fromIndex && typeof fromIndex == 'number') { + index = (fromIndex < 0 ? Math.max(0, index + fromIndex) : Math.min(fromIndex, index - 1)) + 1; + } + while (index--) { + if (array[index] === value) { + return index; + } + } + return -1; } /** - * Checks if `value` is an array. + * 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 3 arguments; (value, index, array). * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is an array, else `false`. + * @category Arrays + * @param {Array} array The array to iterate over. + * @param {Function} [callback] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the maximum value. * @example * - * (function() { return _.isArray(arguments); })(); - * // => false + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 }, + * { 'name': 'curly', 'age': 60 } + * ]; * - * _.isArray([1, 2, 3]); - * // => true + * _.max(stooges, function(stooge) { return stooge.age; }); + * // => { 'name': 'curly', 'age': 60 }; */ - var isArray = nativeIsArray || function(value) { - return toString.call(value) == arrayClass; - }; + function max(array, callback, thisArg) { + var computed = -Infinity, + result = computed; + + if (!array) { + return result; + } + var current, + index = -1, + length = array.length; + + if (!callback) { + while (++index < length) { + if (array[index] > result) { + result = array[index]; + } + } + return result; + } + if (thisArg) { + callback = iteratorBind(callback, thisArg); + } + while (++index < length) { + current = callback(array[index], index, array); + if (current > computed) { + computed = current; + result = array[index]; + } + } + return result; + } /** - * Checks if `value` is a boolean (`true` or `false`) value. + * 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 3 arguments; (value, index, array). * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a boolean value, else `false`. + * @category Arrays + * @param {Array} array The array to iterate over. + * @param {Function} [callback] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Mixed} Returns the minimum value. * @example * - * _.isBoolean(null); - * // => false + * _.min([10, 5, 100, 2, 1000]); + * // => 2 */ - function isBoolean(value) { - return value === true || value === false || toString.call(value) == boolClass; + function min(array, callback, thisArg) { + var computed = Infinity, + result = computed; + + if (!array) { + return result; + } + var current, + index = -1, + length = array.length; + + if (!callback) { + while (++index < length) { + if (array[index] < result) { + result = array[index]; + } + } + return result; + } + if (thisArg) { + callback = iteratorBind(callback, thisArg); + } + while (++index < length) { + current = callback(array[index], index, array); + if (current < computed) { + computed = current; + result = array[index]; + } + } + return result; } /** - * Checks if `value` is a date. + * Creates an array of numbers (positive and/or negative) progressing from + * `start` up to but not including `stop`. This method is a port of Python's + * `range()` function. See http://docs.python.org/library/functions.html#range. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a date, else `false`. + * @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 descrement by. + * @returns {Array} Returns a new range array. * @example * - * _.isDate(new Date); - * // => true + * _.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 isDate(value) { - return toString.call(value) == dateClass; + 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://www.youtube.com/watch?v=XAqIpGU8ZZk#t=16m27s + var index = -1, + length = Math.max(0, Math.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`. Pass `n` to exclude the first `n` values from the result. + * + * @static + * @memberOf _ + * @alias tail + * @category Arrays + * @param {Array} array The array to query. + * @param {Number} [n] The number of elements to return. + * @param {Object} [guard] Internally used to allow this method to work with + * others like `_.map` without using their callback `index` argument for `n`. + * @returns {Array} Returns all but the first value or `n` values of `array`. + * @example + * + * _.rest([3, 2, 1]); + * // => [2, 1] + */ + function rest(array, n, guard) { + if (!array) { + return []; + } + return slice.call(array, (n == null || guard) ? 1 : n); + } + + /** + * Creates a new 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 Arrays + * @param {Array} array The array to shuffle. + * @returns {Array} Returns a new shuffled array. + * @example + * + * _.shuffle([1, 2, 3, 4, 5, 6]); + * // => [4, 1, 6, 3, 5, 2] + */ + function shuffle(array) { + if (!array) { + return []; + } + var rand, + index = -1, + length = array.length, + result = Array(length); + + while (++index < length) { + rand = Math.floor(Math.random() * (index + 1)); + result[index] = result[rand]; + result[rand] = array[index]; + } + return result; + } + + /** + * 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 1 argument; (value). + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to iterate over. + * @param {Mixed} value The value to evaluate. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Number} Returns the index at which the value should be inserted + * into `array`. + * @example + * + * _.sortedIndex([20, 30, 40], 35); + * // => 2 + * + * var dict = { + * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'thirty-five': 35, 'fourty': 40 } + * }; + * + * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { + * return dict.wordToNumber[word]; + * }); + * // => 2 + * + * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { + * return this.wordToNumber[word]; + * }, dict); + * // => 2 + */ + function sortedIndex(array, value, callback, thisArg) { + if (!array) { + return 0; + } + var mid, + low = 0, + high = array.length; + + if (callback) { + if (thisArg) { + callback = bind(callback, thisArg); + } + value = callback(value); + while (low < high) { + mid = (low + high) >>> 1; + callback(array[mid]) < value ? low = mid + 1 : high = mid; + } + } else { + while (low < high) { + mid = (low + high) >>> 1; + array[mid] < value ? low = mid + 1 : high = mid; + } + } + return low; } /** - * Checks if `value` is a DOM element. + * Computes the union of the passed-in arrays using strict equality for + * comparisons, i.e. `===`. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a DOM element, else `false`. + * @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 * - * _.isElement(document.body); - * // => true + * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); + * // => [1, 2, 3, 101, 10] */ - function isElement(value) { - return !!(value && value.nodeType == 1); + function union() { + var index = -1, + result = [], + flattened = concat.apply(result, arguments), + length = flattened.length; + + while (++index < length) { + if (indexOf(result, flattened[index]) < 0) { + result.push(flattened[index]); + } + } + return result; } /** - * Checks if `value` is empty. Arrays or strings with a length of `0` and - * objects with no own enumerable properties are considered "empty". + * 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 a callback` before uniqueness is computed. + * The `callback` is bound to `thisArg` and invoked with 3 arguments; (value, index, array). * * @static * @memberOf _ - * @category Objects - * @param {Array|Object|String} value The value to inspect. - * @returns {Boolean} Returns `true` if the `value` is empty, else `false`. + * @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} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. + * @returns {Array} Returns a duplicate-value-free array. * @example * - * _.isEmpty([1, 2, 3]); - * // => false + * _.uniq([1, 2, 1, 3, 1]); + * // => [1, 2, 3] * - * _.isEmpty({}); - * // => true + * _.uniq([1, 1, 2, 2, 3], true); + * // => [1, 2, 3] + * + * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); }); + * // => [1, 2, 3] + * + * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math); + * // => [1, 2, 3] */ - var isEmpty = createIterator({ - 'args': 'value', - 'init': 'true', - 'top': - 'var className = toString.call(value);\n' + - 'if (className == arrayClass || className == stringClass) return !value.length', - 'inLoop': { - 'object': 'return false' + function uniq(array, isSorted, callback, thisArg) { + var result = []; + if (!array) { + return result; } - }); + var computed, + index = -1, + length = array.length, + seen = []; + + // juggle arguments + if (typeof isSorted == 'function') { + thisArg = callback; + callback = isSorted; + isSorted = false; + } + if (!callback) { + callback = identity; + } else if (thisArg) { + callback = iteratorBind(callback, thisArg); + } + while (++index < length) { + computed = callback(array[index], index, array); + if (isSorted + ? !index || seen[seen.length - 1] !== computed + : indexOf(seen, computed) < 0 + ) { + seen.push(computed); + result.push(array[index]); + } + } + return result; + } /** - * Performs a deep comparison between two values to determine if they are - * equivalent to each other. + * Creates a new array with all occurrences of the passed values removed using + * strict equality for comparisons, i.e. `===`. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} a The value to compare. - * @param {Mixed} b The other value to compare. - * @param {Array} [stack] Internally used to keep track of "seen" objects to - * avoid circular references. - * @returns {Boolean} Returns `true` if the values are equvalent, else `false`. + * @category Arrays + * @param {Array} array The array to filter. + * @param {Mixed} [value1, value2, ...] Values to remove. + * @returns {Array} Returns a new filtered array. * @example * - * var moe = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; - * var clone = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; - * - * moe == clone; - * // => false - * - * _.isEqual(moe, clone); - * // => true + * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); + * // => [2, 3, 4] */ - function isEqual(a, b, stack) { - stack || (stack = []); - - // exit early for identical values - if (a === b) { - // treat `+0` vs. `-0` as not equal - return a !== 0 || (1 / a == 1 / b); - } - // a strict comparison is necessary because `undefined == null` - if (a == null || b == null) { - return a === b; - } - // unwrap any wrapped objects - if (a._chain) { - a = a._wrapped; - } - if (b._chain) { - b = b._wrapped; - } - // invoke a custom `isEqual` method if one is provided - if (a.isEqual && toString.call(a.isEqual) == funcClass) { - return a.isEqual(b); - } - if (b.isEqual && toString.call(b.isEqual) == funcClass) { - return b.isEqual(a); - } - // compare [[Class]] names - var className = toString.call(a); - if (className != toString.call(b)) { - return false; + function without(array) { + var result = []; + if (!array) { + return result; } - switch (className) { - // strings, numbers, dates, and booleans are compared by value - case stringClass: - // primitives and their corresponding object instances are equivalent; - // thus, `'5'` is quivalent to `new String('5')` - return a == String(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 boolClass: - case dateClass: - // coerce dates and booleans to numeric values, dates to milliseconds and booleans to 1 or 0; - // treat invalid dates coerced to `NaN` as not equal - return +a == +b; + var index = -1, + length = array.length, + contains = cachedContains(arguments, 1, 20); - // regexps are compared by their source and flags - case regexpClass: - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; - } - if (typeof a != 'object' || typeof b != 'object') { - return false; - } - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = stack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (stack[length] == a) { - return true; + while (++index < length) { + if (!contains(array[index])) { + result.push(array[index]); } } + return result; + } + /** + * 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', 'curly'], [30, 40, 50], [true, false, false]); + * // => [['moe', 30, true], ['larry', 40, false], ['curly', 50, false]] + */ + function zip(array) { + if (!array) { + return []; + } var index = -1, - result = true, - size = 0; - - // add the first collection to the stack of traversed objects - stack.push(a); - - // recursively compare objects and arrays - if (className == arrayClass) { - // compare array lengths to determine if a deep comparison is necessary - size = a.length; - result = size == b.length; + length = max(pluck(arguments, 'length')), + result = Array(length); - if (result) { - // deep compare the contents, ignoring non-numeric properties - while (size--) { - if (!(result = isEqual(a[size], b[size], stack))) { - break; - } - } - } - } else { - // objects with different constructors are not equivalent - if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) { - return false; - } - // deep compare objects. - for (var prop in a) { - if (hasOwnProperty.call(a, prop)) { - // count the number of properties. - size++; - // deep compare each property value. - if (!(result = hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack))) { - break; - } - } - } - // ensure both objects have the same number of properties - if (result) { - for (prop in b) { - // Adobe's JS engine, embedded in applications like InDesign, has a - // bug that causes `!size--` to throw an error so it must be wrapped - // in parentheses. - // https://github.com/documentcloud/underscore/issues/355 - if (hasOwnProperty.call(b, prop) && !(size--)) { - break; - } - } - result = !size; - } - // handle JScript [[DontEnum]] bug - if (result && hasDontEnumBug) { - while (++index < 7) { - prop = shadowed[index]; - if (hasOwnProperty.call(a, prop)) { - if (!(result = hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack))) { - break; - } - } - } - } + while (++index < length) { + result[index] = pluck(arguments, index); } - // remove the first collection from the stack of traversed objects - stack.pop(); return result; } /** - * Checks if `value` is a finite number. - * Note: This is not the same as native `isFinite`, which will return true for - * booleans and other values. See http://es5.github.com/#x15.1.2.5. + * Creates an object composed from an array of `keys` and an array of `values`. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a finite number, else `false`. + * @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 * - * _.isFinite(-101); - * // => true - * - * _.isFinite('10'); - * // => false - * - * _.isFinite(Infinity); - * // => false + * _.zipObject(['moe', 'larry', 'curly'], [30, 40, 50]); + * // => { 'moe': 30, 'larry': 40, 'curly': 50 } */ - function isFinite(value) { - return nativeIsFinite(value) && toString.call(value) == numberClass; + function zipObject(keys, values) { + if (!keys) { + return {}; + } + var index = -1, + length = keys.length, + result = {}; + + values || (values = []); + while (++index < length) { + result[keys[index]] = values[index]; + } + return result; } + /*--------------------------------------------------------------------------*/ + /** - * Checks if `value` is a function. + * Creates a new function that is restricted to executing only after it is + * called `n` times. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a function, else `false`. + * @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 * - * _.isFunction(''.concat); - * // => true + * var renderNotes = _.after(notes.length, render); + * _.forEach(notes, function(note) { + * note.asyncSave({ 'success': renderNotes }); + * }); + * // `renderNotes` is run once, after all notes have saved */ - function isFunction(value) { - return toString.call(value) == funcClass; + function after(n, func) { + if (n < 1) { + return func(); + } + return function() { + if (--n < 1) { + return func.apply(this, arguments); + } + }; } /** - * Checks if `value` is the language type of Object. - * (e.g. arrays, functions, objects, regexps, `new Number(0)`, and `new String('')`) + * Creates a new 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. Lazy defined methods may be bound by passing + * the object they are bound to as `func` and the method name as `thisArg`. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is an object, else `false`. + * @category Functions + * @param {Function|Object} func The function to bind or the object the method belongs to. + * @param {Mixed} [thisArg] The `this` binding of `func` or the method name. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new bound function. * @example * - * _.isObject({}); - * // => true + * // basic bind + * var func = function(greeting) { + * return greeting + ' ' + this.name; + * }; * - * _.isObject(1); - * // => false + * func = _.bind(func, { 'name': 'moe' }, 'hi'); + * func(); + * // => 'hi moe' + * + * // lazy bind + * var object = { + * 'name': 'moe', + * 'greet': function(greeting) { + * return greeting + ' ' + this.name; + * } + * }; + * + * var func = _.bind(object, 'greet', 'hi'); + * func(); + * // => 'hi moe' + * + * object.greet = function(greeting) { + * return greeting + ', ' + this.name + '!'; + * }; + * + * func(); + * // => 'hi, moe!' */ - function isObject(value) { - // check if the value is the ECMAScript language type of Object - // http://es5.github.com/#x8 - return objectTypes[typeof value] && value !== null; + function bind(func, thisArg) { + var methodName, + isFunc = isFunction(func); + + // juggle arguments + if (!isFunc) { + methodName = thisArg; + thisArg = func; + } + // use `Function#bind` if it exists and is fast + // (in V8 `Function#bind` is slower except when partially applied) + else if (isBindFast || (nativeBind && arguments.length > 2)) { + return nativeBind.call.apply(nativeBind, arguments); + } + + var partialArgs = slice.call(arguments, 2); + + function bound() { + // `Function#bind` spec + // http://es5.github.com/#x15.3.4.5 + var args = arguments, + thisBinding = thisArg; + + if (!isFunc) { + func = thisArg[methodName]; + } + if (partialArgs.length) { + args = args.length + ? partialArgs.concat(slice.call(args)) + : partialArgs; + } + if (this instanceof bound) { + // get `func` instance if `bound` is invoked in a `new` expression + noop.prototype = func.prototype; + thisBinding = new noop; + + // mimic the constructor's `return` behavior + // http://es5.github.com/#x13.2.2 + var result = func.apply(thisBinding, args); + return result && objectTypes[typeof result] + ? result + : thisBinding + } + return func.apply(thisBinding, args); + } + return bound; } /** - * 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. + * Binds methods on `object` to `object`, overwriting the existing method. + * If no method names are provided, all the function properties of `object` + * will be bound. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is `NaN`, else `false`. + * @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 * - * _.isNaN(NaN); - * // => true - * - * _.isNaN(new Number(NaN)); - * // => true - * - * isNaN(undefined); - * // => true + * var buttonView = { + * 'label': 'lodash', + * 'onClick': function() { alert('clicked: ' + this.label); } + * }; * - * _.isNaN(undefined); - * // => false + * _.bindAll(buttonView); + * jQuery('#lodash_button').on('click', buttonView.onClick); + * // => When the button is clicked, `this.label` will have the correct value */ - 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 toString.call(value) == numberClass && value != +value - } + var bindAll = createIterator({ + 'useHas': false, + 'useStrict': false, + 'args': 'object', + 'init': 'object', + 'top': + 'var funcs = arguments,\n' + + ' length = funcs.length;\n' + + 'if (length > 1) {\n' + + ' for (var index = 1; index < length; index++) {\n' + + ' result[funcs[index]] = bind(result[funcs[index]], result)\n' + + ' }\n' + + ' return result\n' + + '}', + 'inLoop': + 'if (isFunction(result[index])) {\n' + + ' result[index] = bind(result[index], result)\n' + + '}' + }); /** - * Checks if `value` is `null`. + * Creates a new function that is the composition of the passed functions, + * where each function consumes the return value of the function that follows. + * In math terms, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is `null`, else `false`. + * @category Functions + * @param {Function} [func1, func2, ...] Functions to compose. + * @returns {Function} Returns the new composed function. * @example * - * _.isNull(null); - * // => true - * - * _.isNull(undefined); - * // => false + * var greet = function(name) { return 'hi: ' + name; }; + * var exclaim = function(statement) { return statement + '!'; }; + * var welcome = _.compose(exclaim, greet); + * welcome('moe'); + * // => 'hi: moe!' */ - function isNull(value) { - return value === null; + function compose() { + var funcs = arguments; + return function() { + var args = arguments, + length = funcs.length; + + while (length--) { + args = [funcs[length].apply(this, args)]; + } + return args[0]; + }; } /** - * Checks if `value` is a number. + * Creates a new function that will delay the execution of `func` until after + * `wait` milliseconds have elapsed since the last time it was invoked. Pass + * `true` for `immediate` to cause debounce to invoke `func` on the leading, + * instead of the trailing, edge of the `wait` timeout. Subsequent calls to + * the debounced function will return the result of the last `func` call. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a number, else `false`. + * @category Functions + * @param {Function} func The function to debounce. + * @param {Number} wait The number of milliseconds to delay. + * @param {Boolean} immediate A flag to indicate execution is on the leading + * edge of the timeout. + * @returns {Function} Returns the new debounced function. * @example * - * _.isNumber(8.4 * 5; - * // => true + * var lazyLayout = _.debounce(calculateLayout, 300); + * jQuery(window).on('resize', lazyLayout); */ - function isNumber(value) { - return toString.call(value) == numberClass; + function debounce(func, wait, immediate) { + var args, + result, + thisArg, + timeoutId; + + function delayed() { + timeoutId = null; + if (!immediate) { + func.apply(thisArg, args); + } + } + + return function() { + var isImmediate = immediate && !timeoutId; + args = arguments; + thisArg = this; + + clearTimeout(timeoutId); + timeoutId = setTimeout(delayed, wait); + + if (isImmediate) { + result = func.apply(thisArg, args); + } + return result; + }; } /** - * Checks if `value` is a regular expression. + * Executes the `func` function after `wait` milliseconds. Additional arguments + * will be passed to `func` when it is invoked. * * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a regular expression, else `false`. + * @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 `setTimeout` timeout id. * @example * - * _.isRegExp(/moe/); - * // => true + * var log = _.bind(console.log, console); + * _.delay(log, 1000, 'logged later'); + * // => 'logged later' (Appears after one second.) */ - function isRegExp(value) { - return toString.call(value) == regexpClass; + function delay(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function() { return func.apply(undefined, args); }, wait); } /** - * Checks if `value` is a string. + * 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 Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a string, else `false`. + * @category Functions + * @param {Function} func The function to defer. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. + * @returns {Number} Returns the `setTimeout` timeout id. * @example * - * _.isString('moe'); - * // => true + * _.defer(function() { alert('deferred'); }); + * // returns from the function before `alert` is called */ - function isString(value) { - return toString.call(value) == stringClass; + function defer(func) { + var args = slice.call(arguments, 1); + return setTimeout(function() { return func.apply(undefined, args); }, 1); } /** - * Checks if `value` is `undefined`. + * Creates a new 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. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is `undefined`, else `false`. + * @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 * - * _.isUndefined(void 0); - * // => true + * var fibonacci = _.memoize(function(n) { + * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); + * }); */ - function isUndefined(value) { - return value === undefined; + function memoize(func, resolver) { + var cache = {}; + return function() { + var prop = resolver ? resolver.apply(this, arguments) : arguments[0]; + return hasOwnProperty.call(cache, prop) + ? cache[prop] + : (cache[prop] = func.apply(this, arguments)); + }; } /** - * Produces an array of object`'s own enumerable property names. + * Creates a new function that is restricted to one execution. Repeat calls to + * the function will return the value of the first call. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property names. + * @category Functions + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. * @example * - * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); - * // => ['one', 'two', 'three'] (order is not guaranteed) + * var initialize = _.once(createApplication); + * initialize(); + * initialize(); + * // Application is only created once. */ - var keys = !nativeKeys ? shimKeys : function(object) { - // avoid iterating over the `prototype` property - return typeof object == 'function' - ? shimKeys(object) - : nativeKeys(object); - }; + function once(func) { + var result, + ran = false; + + 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 an object composed of the specified properties. Property names may - * be specified as individual arguments or as arrays of property names. + * Creates a new function that, when called, invokes `func` with any additional + * `partial` arguments prepended to those passed to the new function. This method + * is similar `bind`, except it does **not** alter the `this` binding. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to pluck. - * @param {Object} [prop1, prop2, ...] The properties to pick. - * @returns {Object} Returns an object composed of the picked properties. + * @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 * - * _.pick({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'name', 'age'); - * // => { 'name': 'moe', 'age': 40 } + * var greet = function(greeting, name) { return greeting + ': ' + name; }; + * var hi = _.partial(greet, 'hi'); + * hi('moe'); + * // => 'hi: moe' */ - function pick(object) { - var prop, - index = 0, - props = concat.apply(ArrayProto, arguments), - length = props.length, - result = {}; + function partial(func) { + var args = slice.call(arguments, 1), + argsLength = args.length; - // start `index` at `1` to skip `object` - while (++index < length) { - prop = props[index]; - if (prop in object) { - result[prop] = object[prop]; + return function() { + var result, + others = arguments; + + if (others.length) { + args.length = argsLength; + push.apply(args, others); } - } - return result; + result = args.length == 1 ? func.call(this, args[0]) : func.apply(this, args); + args.length = argsLength; + return result; + }; } /** - * Gets the size of `value` by returning `value.length` if `value` is a string - * or array, or the number of own enumerable properties if `value` is an object. + * Creates a new function that, when executed, will only call the `func` + * function at most once per every `wait` milliseconds. If the throttled + * function is invoked more than once during the `wait` timeout, `func` will + * also be called on the trailing edge of the timeout. Subsequent calls to the + * throttled function will return the result of the last `func` call. * - * @deprecated * @static * @memberOf _ - * @category Objects - * @param {Array|Object|String} value The value to inspect. - * @returns {Number} Returns `value.length` if `value` is a string or array, - * or the number of own enumerable properties if `value` is an object. + * @category Functions + * @param {Function} func The function to throttle. + * @param {Number} wait The number of milliseconds to throttle executions to. + * @returns {Function} Returns the new throttled function. * @example * - * _.size([1, 2]); - * // => 2 - * - * _.size({ 'one': 1, 'two': 2, 'three': 3 }); - * // => 3 - * - * _.size('curly'); - * // => 5 + * var throttled = _.throttle(updatePosition, 100); + * jQuery(window).on('scroll', throttled); */ - function size(value) { - if (!value) { - return 0; + function throttle(func, wait) { + var args, + result, + thisArg, + timeoutId, + lastCalled = 0; + + function trailingCall() { + lastCalled = new Date; + timeoutId = null; + func.apply(thisArg, args); } - var length = value.length; - return length === length >>> 0 ? value.length : keys(value).length; + + return function() { + var now = new Date, + remain = wait - (now - lastCalled); + + args = arguments; + thisArg = this; + + if (remain <= 0) { + lastCalled = now; + result = func.apply(thisArg, args); + } + else if (!timeoutId) { + timeoutId = setTimeout(trailingCall, remain); + } + return result; + }; } /** - * Produces an array of `object`'s own enumerable property values. + * Creates a new function that passes `value` to the `wrapper` function as its + * first argument. Additional arguments passed to the new function are appended + * to those passed to the `wrapper` function. * * @static * @memberOf _ - * @category Objects - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property values. + * @category Functions + * @param {Mixed} value The value to wrap. + * @param {Function} wrapper The wrapper function. + * @returns {Function} Returns the new function. * @example * - * _.values({ 'one': 1, 'two': 2, 'three': 3 }); - * // => [1, 2, 3] + * var hello = function(name) { return 'hello: ' + name; }; + * hello = _.wrap(hello, function(func) { + * return 'before, ' + func('moe') + ', after'; + * }); + * hello(); + * // => 'before, hello: moe, after' */ - var values = createIterator({ - 'args': 'object', - 'init': '[]', - 'inLoop': 'result.push(object[index])' - }); + function wrap(value, wrapper) { + return function() { + var args = [value]; + if (arguments.length) { + push.apply(args, arguments); + } + return wrapper.apply(this, args); + }; + } /*--------------------------------------------------------------------------*/ /** - * Escapes a string for inclusion in HTML, replacing `&`, `<`, `"`, and `'` - * characters. + * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their + * corresponding HTML entities. * * @static * @memberOf _ @@ -3919,8 +4806,8 @@ * @returns {String} Returns the escaped string. * @example * - * _.escape('Curly, Larry & Moe'); - * // => "Curly, Larry & Moe" + * _.escape('Moe, Larry & Curly'); + * // => "Moe, Larry & Curly" */ function escape(string) { return string == null ? '' : (string + '').replace(reUnescapedHtml, escapeHtmlChar); @@ -3928,6 +4815,7 @@ /** * This function returns the first argument passed to it. + * * Note: It is used throughout Lo-Dash as a default callback. * * @static @@ -3961,11 +4849,11 @@ * } * }); * - * _.capitalize('curly'); - * // => 'Curly' - * - * _('larry').capitalize(); + * _.capitalize('larry'); * // => 'Larry' + * + * _('curly').capitalize(); + * // => 'Curly' */ function mixin(object) { forEach(functions(object), function(methodName) { @@ -4037,13 +4925,20 @@ return null; } var value = object[property]; - return toString.call(value) == funcClass ? object[property]() : value; + 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 + * + * Note: Lo-Dash may be used in Chrome extensions by either creating a `lodash csp` + * build and avoiding `_.template` use, or loading Lo-Dash in a sandboxed page. + * See http://developer.chrome.com/trunk/extensions/sandboxingEval.html + * * @static * @memberOf _ * @category Utilities @@ -4054,41 +4949,47 @@ * is given, else it returns the interpolated text. * @example * - * // using compiled template + * // 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', 'curly', 'larry'] }); - * // => '
  • moe
  • curly
  • larry
  • ' + * _.template(list, { 'people': ['moe', 'larry', 'curly'] }); + * // => '
  • moe
  • larry
  • curly
  • ' * - * var template = _.template('<%- value %>'); - * template({ 'value': ' + * // using the `source` property to inline compiled templates for meaningful + * // line numbers in error messages and a stack trace + * fs.writeFileSync(path.join(cwd, 'jst.js'), '\ + * var JST = {\ + * "main": ' + _.template(mainText).source + '\ + * };\ + * '); */ function template(text, data, options) { // based on John Resig's `tmpl` implementation @@ -4096,25 +4997,28 @@ // and Laura Doktorova's doT.js // https://github.com/olado/doT options || (options = {}); + text += ''; var isEvaluating, - isInterpolating, result, - defaults = lodash.templateSettings, escapeDelimiter = options.escape, evaluateDelimiter = options.evaluate, interpolateDelimiter = options.interpolate, - variable = options.variable; + settings = lodash.templateSettings, + variable = options.variable || settings.variable, + hasVariable = variable; - // use template defaults if no option is provided + // use default settings if no options object is provided if (escapeDelimiter == null) { - escapeDelimiter = defaults.escape; + escapeDelimiter = settings.escape; } if (evaluateDelimiter == null) { - evaluateDelimiter = defaults.evaluate; + // use `false` as the fallback value, instead of leaving it `undefined`, + // so the initial assignment of `reEvaluateDelimiter` will still occur + evaluateDelimiter = settings.evaluate || false; } if (interpolateDelimiter == null) { - interpolateDelimiter = defaults.interpolate; + interpolateDelimiter = settings.interpolate; } // tokenize delimiters to avoid escaping them @@ -4122,37 +5026,66 @@ text = text.replace(escapeDelimiter, tokenizeEscape); } if (interpolateDelimiter) { - isInterpolating = text != (text = text.replace(interpolateDelimiter, tokenizeInterpolate)); + text = text.replace(interpolateDelimiter, tokenizeInterpolate); } - if (evaluateDelimiter) { - isEvaluating = text != (text = text.replace(evaluateDelimiter, tokenizeEvaluate)); + if (evaluateDelimiter != lastEvaluateDelimiter) { + // generate `reEvaluateDelimiter` to match `_.templateSettings.evaluate` + // and internal ``, `` delimiters + lastEvaluateDelimiter = evaluateDelimiter; + reEvaluateDelimiter = RegExp( + '|' + + (evaluateDelimiter ? '|' + evaluateDelimiter.source : '') + , 'g'); } + isEvaluating = tokenized.length; + text = text.replace(reEvaluateDelimiter, tokenizeEvaluate); + isEvaluating = isEvaluating != tokenized.length; // escape characters that cannot be included in string literals and // detokenize delimiter code snippets - text = "__p='" + text + text = "__p += '" + text .replace(reUnescapedString, escapeStringChar) .replace(reToken, detokenize) + "';\n"; // clear stored code snippets tokenized.length = 0; - // if `options.variable` is not specified, add `data` to the top of the scope chain - if (!variable) { - variable = defaults.variable; - text = 'with (' + variable + ' || {}) {\n' + text + '\n}\n'; + // if `variable` is not specified and the template contains "evaluate" + // delimiters, wrap a with-statement around the generated code to add the + // data object to the top of the scope chain + if (!hasVariable) { + variable = lastVariable || 'obj'; + + if (isEvaluating) { + text = 'with (' + variable + ') {\n' + text + '\n}\n'; + } + else { + if (variable != lastVariable) { + // generate `reDoubleVariable` to match references like `obj.obj` inside + // transformed "escape" and "interpolate" delimiters + lastVariable = variable; + reDoubleVariable = RegExp('(\\(\\s*)' + variable + '\\.' + variable + '\\b', 'g'); + } + // avoid a with-statement by prepending data object references to property names + text = text + .replace(reInsertVariable, '$&' + variable + '.') + .replace(reDoubleVariable, '$1__d'); + } } + // cleanup code by stripping empty strings + text = ( isEvaluating ? text.replace(reEmptyStringLeading, '') : text) + .replace(reEmptyStringMiddle, '$1') + .replace(reEmptyStringTrailing, '$1;'); + + // frame code as the function body text = 'function(' + variable + ') {\n' + - 'var __p' + - (isInterpolating - ? ', __t' - : '' - ) + + (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') + + 'var __t, __p = \'\', __e = _.escape' + (isEvaluating ? ', __j = Array.prototype.join;\n' + 'function print() { __p += __j.call(arguments, \'\') }\n' - : ';\n' + : (hasVariable ? '' : ', __d = ' + variable + '.' + variable + ' || ' + variable) + ';\n' ) + text + 'return __p\n}'; @@ -4163,14 +5096,21 @@ text += '\n//@ sourceURL=/lodash/template/source[' + (templateCounter++) + ']'; } - result = Function('_', 'return ' + text)(lodash); + try { + result = Function('_', 'return ' + text)(lodash); + } catch(e) { + // defer syntax errors until the compiled template is executed to allow + // examining the `source` property beforehand and for consistency, + // because other template related errors occur at execution + result = function() { throw e; }; + } if (data) { return result(data); } - // provide the compiled function's source via its `toString()` method, in + // provide the compiled function's source via its `toString` method, in // supported environments, or the `source` property as a convenience for - // build time precompilation + // inlining compiled templates during the build process result.source = text; return result; } @@ -4206,6 +5146,24 @@ } } + /** + * Converts the HTML entities `&`, `<`, `>`, `"`, and `'` + * in `string` to their corresponding characters. + * + * @static + * @memberOf _ + * @category Utilities + * @param {String} string The string to unescape. + * @returns {String} Returns the unescaped string. + * @example + * + * _.unescape('Moe, Larry & Curly'); + * // => "Moe, Larry & Curly" + */ + function unescape(string) { + return string == null ? '' : (string + '').replace(reEscapedHtml, unescapeHtmlChar); + } + /** * Generates a unique id. If `prefix` is passed, the id will be appended to it. * @@ -4264,7 +5222,7 @@ * @static * @memberOf _ * @category Chaining - * @param {Mixed} value The value to pass to `callback`. + * @param {Mixed} value The value to pass to `interceptor`. * @param {Function} interceptor The function to invoke. * @returns {Mixed} Returns `value`. * @example @@ -4325,7 +5283,7 @@ * @memberOf _ * @type String */ - lodash.VERSION = '0.3.2'; + lodash.VERSION = '0.6.1'; // assign static methods lodash.after = after; @@ -4336,11 +5294,13 @@ lodash.compact = compact; lodash.compose = compose; lodash.contains = contains; + lodash.countBy = countBy; lodash.debounce = debounce; lodash.defaults = defaults; lodash.defer = defer; lodash.delay = delay; lodash.difference = difference; + lodash.drop = drop; lodash.escape = escape; lodash.every = every; lodash.extend = extend; @@ -4381,6 +5341,7 @@ lodash.map = map; lodash.max = max; lodash.memoize = memoize; + lodash.merge = merge; lodash.min = min; lodash.mixin = mixin; lodash.noConflict = noConflict; @@ -4404,13 +5365,16 @@ lodash.throttle = throttle; lodash.times = times; lodash.toArray = toArray; + lodash.unescape = unescape; lodash.union = union; lodash.uniq = uniq; lodash.uniqueId = uniqueId; lodash.values = values; + lodash.where = where; lodash.without = without; lodash.wrap = wrap; lodash.zip = zip; + lodash.zipObject = zipObject; // assign aliases lodash.all = every; @@ -4424,6 +5388,7 @@ lodash.include = contains; lodash.inject = reduce; lodash.methods = functions; + lodash.omit = drop; lodash.select = filter; lodash.tail = rest; lodash.take = first; @@ -4454,12 +5419,9 @@ var value = this._wrapped; func.apply(value, arguments); - // IE compatibility mode and IE < 9 have buggy Array `shift()` and `splice()` - // functions that fail to remove the last element, `value[0]`, of - // array-like objects even though the `length` property is set to `0`. - // The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` - // is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. - if (value.length === 0) { + // avoid array-like object bugs with `Array#shift` and `Array#splice` in + // Firefox < 10 and IE < 9 + if (hasObjectSpliceBug && value.length === 0) { delete value[0]; } if (this._chain) { @@ -5539,7 +6501,7 @@ if (!JSON) { })(this); /** -* Miso.Dataset - v0.2.1 - 8/16/2012 +* Miso.Dataset - v0.2.2 - 9/3/2012 * http://github.com/misoproject/dataset * Copyright (c) 2012 Alex Graul, Irene Ros; * Dual Licensed: MIT, GPL @@ -5573,29 +6535,37 @@ if (!JSON) { mixed : { name : 'mixed', coerce : function(v) { + if (_.isNull(v) || typeof v === "undefined" || _.isNaN(v)) { + return null; + } return v; }, test : function(v) { return true; }, compare : function(s1, s2) { - if (s1 < s2) { return -1; } - if (s1 > s2) { return 1; } - return 0; + if ( _.isEqual(s1, s2) ) { return 0; } + if (s1 < s2) { return -1;} + if (s1 > s2) { return 1; } }, numeric : function(v) { - return _.isNaN( Number(v) ) ? null : Number(v); + return v === null || _.isNaN(+v) ? null : +v; } }, string : { name : "string", coerce : function(v) { - return v == null ? null : v.toString(); + if (_.isNaN(v) || v === null || typeof v === "undefined") { + return null; + } + return v.toString(); }, + test : function(v) { return (v === null || typeof v === "undefined" || typeof v === 'string'); }, + compare : function(s1, s2) { if (s1 == null && s2 != null) { return -1; } if (s1 != null && s2 == null) { return 1; } @@ -5619,6 +6589,9 @@ if (!JSON) { name : "boolean", regexp : /^(true|false)$/, coerce : function(v) { + if (_.isNaN(v) || v === null || typeof v === "undefined") { + return null; + } if (v === 'false') { return false; } return Boolean(v); }, @@ -5637,7 +6610,7 @@ if (!JSON) { return (n1 < n2 ? -1 : 1); }, numeric : function(value) { - if (_.isNaN(value)) { + if (value === null || _.isNaN(value)) { return null; } else { return (value) ? 1 : 0; @@ -5649,10 +6622,11 @@ if (!JSON) { name : "number", regexp : /^\s*[\-\.]?[0-9]+([\.][0-9]+)?\s*$/, coerce : function(v) { - if (_.isNull(v)) { + var cv = +v; + if (_.isNull(v) || typeof v === "undefined" || _.isNaN(cv)) { return null; } - return _.isNaN(v) ? null : +v; + return cv; }, test : function(v) { if (v === null || typeof v === "undefined" || typeof v === 'number' || this.regexp.test( v ) ) { @@ -5669,6 +6643,9 @@ if (!JSON) { return (n1 < n2 ? -1 : 1); }, numeric : function(value) { + if (_.isNaN(value) || value === null) { + return null; + } return value; } }, @@ -5718,6 +6695,11 @@ if (!JSON) { coerce : function(v, options) { options = options || {}; + + if (_.isNull(v) || typeof v === "undefined" || _.isNaN(v)) { + return null; + } + // if string, then parse as a time if (_.isString(v)) { var format = options.format || this.format; @@ -5750,6 +6732,9 @@ if (!JSON) { return 0; }, numeric : function( value ) { + if (_.isNaN(value) || value === null) { + return null; + } return value.valueOf(); } } @@ -6104,6 +7089,35 @@ if (!JSON) { }, this); }, + /** + * If this is a computed column, it calculates the value + * for this column and adds it to the data. + * Parameters: + * row - the row from which column is computed. + * i - Optional. the index at which this value will get added. + * Returns + * val - the computed value + */ + compute : function(row, i) { + if (this.func) { + var val = this.func(row); + if (typeof i !== "undefined") { + this.data[i] = val; + } else { + this.data.push(val); + } + + return val; + } + }, + + /** + * returns true if this is a computed column. False otherwise. + */ + isComputed : function() { + return !_.isUndefined(this.func); + }, + _sum : function() { return _.sum(this.data); }, @@ -6533,6 +7547,11 @@ if (!JSON) { _.each(row, function(value, key) { var column = this.column(key); + // is this a computed column? if so throw an error + if (column.isComputed()) { + throw "You're trying to update a computed column. Those get computed!"; + } + // if we suddenly see values for data that didn't exist before as a column // just drop it. First fetch defines the column structure. if (typeof column !== "undefined") { @@ -6558,12 +7577,22 @@ if (!JSON) { } }, this); + // do we have any computed columns? If so we need to calculate their values. + if (this._computedColumns) { + _.each(this._computedColumns, function(column) { + var newVal = column.compute(row); + row[column.name] = newVal; + }); + } + // if we don't have a comparator, just append them at the end. if (_.isUndefined(this.comparator)) { // add all data _.each(this._columns, function(column) { - column.data.push(!_.isUndefined(row[column.name]) && !_.isNull(row[column.name]) ? row[column.name] : null); + if (!column.isComputed()) { + column.data.push(!_.isUndefined(row[column.name]) && !_.isNull(row[column.name]) ? row[column.name] : null); + } }); this.length++; @@ -7010,6 +8039,7 @@ Version 0.0.1.2 this._columns = []; this._columnPositionByName = {}; + this._computedColumns = []; if (typeof options !== "undefined") { options = options || {}; @@ -7317,6 +8347,46 @@ Version 0.0.1.2 }, this); }, + /** + * Allows adding of a computed column. A computed column is + * a column that is somehow based on the other existing columns. + * Parameters: + * name : name of new column + * type : The type of the column based on existing types. + * func : The way that the column is derived. It takes a row as a parameter. + */ + addComputedColumn : function(name, type, func) { + // check if we already ahve a column by this name. + if ( !_.isUndefined(this.column(name)) ) { + throw "There is already a column by this name."; + } else { + + // check that this is a known type. + if (typeof Miso.types[type] === "undefined") { + throw "The type " + type + " doesn't exist"; + } + + var column = new Miso.Column({ + name : name, + type : type, + func : _.bind(func, this) + }); + + this._columns.push(column); + this._computedColumns.push(column); + this._columnPositionByName[column.name] = this._columns.length - 1; + + // do we already have data? if so compute the values for this column. + if (this.length > 0) { + this.each(function(row, i) { + column.compute(row, i); + }, this); + } + + return column; + } + }, + /** * Adds a single column to the dataset * Parameters: @@ -7480,8 +8550,15 @@ Version 0.0.1.2 newKeys = _.keys(props); _.each(newKeys, function(columnName) { + c = this.column(columnName); + // check if we're trying to update a computed column. If so + // fail. + if (c.isComputed()) { + throw "You're trying to update a computed column. Those get computed!"; + } + // test if the value passes the type test var Type = Miso.types[c.type]; @@ -7503,6 +8580,23 @@ Version 0.0.1.2 } c.data[rowIndex] = props[c.name]; }, this); + + // do we have any computed columns? if so we need to update + // the row. + if (typeof this._computedColumns !== "undefined") { + _.each(this._computedColumns, function(column) { + + // compute the complete row: + var newrow = _.extend({}, row, props); + + var oldValue = newrow[column.name]; + var newValue = column.compute(newrow, rowIndex); + // if this is actually a new value, then add it to the delta. + if (oldValue !== newValue) { + props[column.name] = newValue; + } + }); + } deltas.push( { _id : row._id, old : row, changed : props } ); }, this); diff --git a/dist/miso.ds.deps.ie.min.0.2.1.js b/dist/miso.ds.deps.ie.min.0.2.1.js deleted file mode 100644 index 39c8f33..0000000 --- a/dist/miso.ds.deps.ie.min.0.2.1.js +++ /dev/null @@ -1,9 +0,0 @@ -/** -* Miso.Dataset - v0.2.1 - 6/29/2012 -* http://github.com/misoproject/dataset -* Copyright (c) 2012 Alex Graul, Irene Ros; -* Dual Licensed: MIT, GPL -* https://github.com/misoproject/dataset/blob/master/LICENSE-MIT -* https://github.com/misoproject/dataset/blob/master/LICENSE-GPL -*/ -(function(a,b){function z(a,b){this._d=a,this._isUTC=!!b}function A(a){return a<0?Math.ceil(a):Math.floor(a)}function B(a){var b=this._data={},c=a.years||a.y||0,d=a.months||a.M||0,e=a.weeks||a.w||0,f=a.days||a.d||0,g=a.hours||a.h||0,h=a.minutes||a.m||0,i=a.seconds||a.s||0,j=a.milliseconds||a.ms||0;this._milliseconds=j+i*1e3+h*6e4+g*36e5,this._days=f+e*7,this._months=d+c*12,b.milliseconds=j%1e3,i+=A(j/1e3),b.seconds=i%60,h+=A(i/60),b.minutes=h%60,g+=A(h/60),b.hours=g%24,f+=A(g/24),f+=e*7,b.days=f%30,d+=A(f/30),b.months=d%12,c+=A(d/12),b.years=c}function C(a,b){var c=a+"";while(c.length11?"pm":"am";case"A":return o?o(h,i,!0):h>11?"PM":"AM";case"H":return h;case"HH":return C(h,2);case"h":return h%12||12;case"hh":return C(h%12||12,2);case"m":return i;case"mm":return C(i,2);case"s":return j;case"ss":return C(j,2);case"S":return~~(l/100);case"SS":return C(~~(l/10),2);case"SSS":return C(l,3);case"Z":return(m<0?"-":"+")+C(~~(Math.abs(m)/60),2)+":"+C(~~(Math.abs(m)%60),2);case"ZZ":return(m<0?"-":"+")+C(~~(10*Math.abs(m)/6),4);case"L":case"LL":case"LLL":case"LLLL":case"LT":return G(b,moment.longDateFormat[c]);default:return c.replace(/(^\[)|(\\)|\]$/g,"")}}var d=b.month(),e=b.date(),f=b.year(),g=b.day(),h=b.hours(),i=b.minutes(),j=b.seconds(),l=b.milliseconds(),m=-b.zone(),n=moment.ordinal,o=moment.meridiem;return c.replace(k,p)}function H(a){switch(a){case"DDDD":return o;case"YYYY":return p;case"S":case"SS":case"SSS":case"DDD":return n;case"MMM":case"MMMM":case"ddd":case"dddd":case"a":case"A":return q;case"Z":case"ZZ":return r;case"T":return s;case"MM":case"DD":case"dd":case"YY":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":return m;default:return new RegExp(a.replace("\\",""))}}function I(a,b,c,d){var e;switch(a){case"M":case"MM":c[1]=b==null?0:~~b-1;break;case"MMM":case"MMMM":for(e=0;e<12;e++)if(moment.monthsParse[e].test(b)){c[1]=e;break}break;case"D":case"DD":case"DDD":case"DDDD":c[2]=~~b;break;case"YY":b=~~b,c[0]=b+(b>70?1900:2e3);break;case"YYYY":c[0]=~~Math.abs(b);break;case"a":case"A":d.isPm=(b+"").toLowerCase()==="pm";break;case"H":case"HH":case"h":case"hh":c[3]=~~b;break;case"m":case"mm":c[4]=~~b;break;case"s":case"ss":c[5]=~~b;break;case"S":case"SS":case"SSS":c[6]=~~(("0."+b)*1e3);break;case"Z":case"ZZ":d.isUTC=!0,e=(b+"").match(w),e&&e[1]&&(d.tzh=~~e[1]),e&&e[2]&&(d.tzm=~~e[2]),e&&e[0]==="+"&&(d.tzh=-d.tzh,d.tzm=-d.tzm)}}function J(b,c){var d=[0,0,1,0,0,0,0],e={tzh:0,tzm:0},f=c.match(k),g,h;for(g=0;g0,N.apply({},i)}function P(a,b){moment.fn[a]=function(a){var c=this._isUTC?"UTC":"";return a!=null?(this._d["set"+c+b](a),this):this._d["get"+c+b]()}}function Q(a){moment.duration.fn[a]=function(){return this._data[a]}}function R(a,b){moment.duration.fn["as"+a]=function(){return+this/b}}var moment,c="1.6.2",d=Math.round,e,f={},g="en",h=typeof module!="undefined",i="months|monthsShort|monthsParse|weekdays|weekdaysShort|longDateFormat|calendar|relativeTime|ordinal|meridiem".split("|"),j=/^\/?Date\((\-?\d+)/i,k=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|dddd?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?|LT|LL?L?L?)/g,l=/([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi,m=/\d\d?/,n=/\d{1,3}/,o=/\d{3}/,p=/\d{4}/,q=/[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i,r=/Z|[\+\-]\d\d:?\d\d/i,s=/T/i,t=/^\s*\d{4}-\d\d-\d\d(T(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,u="YYYY-MM-DDTHH:mm:ssZ",v=[["HH:mm:ss.S",/T\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/T\d\d:\d\d:\d\d/],["HH:mm",/T\d\d:\d\d/],["HH",/T\d\d/]],w=/([\+\-]|\d\d)/gi,x="Month|Date|Hours|Minutes|Seconds|Milliseconds".split("|"),y={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6};moment=function(c,d){if(c===null||c==="")return null;var e,f,g;return moment.isMoment(c)?(e=new a(+c._d),g=c._isUTC):d?E(d)?e=L(c,d):e=J(c,d):(f=j.exec(c),e=c===b?new a:f?new a(+f[1]):c instanceof a?c:E(c)?F(c):typeof c=="string"?M(c):new a(c)),new z(e,g)},moment.utc=function(b,c){return E(b)?new z(new a(a.UTC.apply({},b)),!0):c&&b?moment(b+" +0000",c+" Z").utc():moment(b&&!r.exec(b)?b+"+0000":b).utc()},moment.unix=function(a){return moment(a*1e3)},moment.duration=function(a,b){var c=moment.isDuration(a),d=typeof a=="number",e=c?a._data:d?{}:a;return d&&(b?e[b]=a:e.milliseconds=a),new B(e)},moment.humanizeDuration=function(a,b,c){return moment.duration(a,b===!0?null:b).humanize(b===!0?!0:c)},moment.version=c,moment.defaultFormat=u,moment.lang=function(a,b){var c,d,e=[];if(!a)return g;if(b){for(c=0;c<12;c++)e[c]=new RegExp("^"+b.months[c]+"|^"+b.monthsShort[c].replace(".",""),"i");b.monthsParse=b.monthsParse||e,f[a]=b}if(f[a]){for(c=0;cc?1:0}function Z(a,b){return n[b]}function $(a){return"\\"+s[a]}function _(a){return q[a]}function ba(a,b){return function(c,d,e){return a.call(b,c,d,e)}}function bb(){}function bd(a,b){var c=n.length;return n[c]="'+\n_.escape("+b+") +\n'",m+c}function be(a,b){var c=n.length;return n[c]="'+\n((__t = ("+b+")) == null ? '' : __t) +\n'",m+c}function bf(a,b){var c=n.length;return n[c]="';\n"+b+";\n__p += '",m+c}function br(a,b,c,d){if(!a)return c;var e=a.length,f=arguments.length<3;d&&(b=ba(b,d));if(e===e>>>0){e&&f&&(c=a[--e]);while(e--)c=b(c,a[e],e,a);return c}var g,h=cw(a);e=h.length,e&&f&&(c=a[h[--e]]);while(e--)g=h[e],c=b(c,a[g],g,a);return c}function bu(a,b,c){if(typeof b=="string"){var d=b;b=function(a){return a[d]}}else c&&(b=ba(b,c));var e=bg(a,b).sort(Y),f=e.length;while(f--)e[f]=e[f].value;return e}function bv(a){if(!a)return[];if(a.toArray&&G.call(a.toArray)==w)return a.toArray();var b=a.length;return b===b>>>0?F.call(a):cz(a)}function bw(a){var b=[];if(!a)return b;var c=-1,d=a.length;while(++c-1})&&b.push(c);return b}function bD(a,b,c){if(a){var d=a.length;return b==null||c?a[d-1]:F.call(a,-b||d)}}function bE(a,b,c){if(!a)return-1;var d=a.length;c&&typeof c=="number"&&(d=(c<0?Math.max(0,d+c):Math.min(c,d-1))+1);while(d--)if(a[d]===b)return d;return-1}function bF(a,b,c){var d=-Infinity,e=d;if(!a)return e;var f,g=-1,h=a.length;if(!b){while(++ge&&(e=a[g]);return e}c&&(b=ba(b,c));while(++gd&&(d=f,e=a[g]);return e}function bG(a,b,c){var d=Infinity,e=d;if(!a)return e;var f,g=-1,h=a.length;if(!b){while(++g>>1,c(a[e])>>1,a[e]>>0?a.length:cw(a).length}function cA(a){return a==null?"":(a+"").replace(i,_)}function cB(a){return a}function cC(a){bl(ce(a),function(b){var c=N[b]=a[b];O.prototype[b]=function(){var a=[this._wrapped];arguments.length&&E.apply(a,arguments);var b=c.apply(N,a);return this._chain&&(b=new O(b),b._chain=!0),b}})}function cD(){return a._=f,this}function cE(a,b){if(!a)return null;var c=a[b];return G.call(c)==w?a[b]():c}function cF(a,b,c){c||(c={});var d,e,f,g=N.templateSettings,i=c.escape,k=c.evaluate,m=c.interpolate,p=c.variable;return i==null&&(i=g.escape),k==null&&(k=g.evaluate),m==null&&(m=g.interpolate),i&&(a=a.replace(i,bd)),m&&(e=a!=(a=a.replace(m,be))),k&&(d=a!=(a=a.replace(k,bf))),a="__p='"+a.replace(j,$).replace(h,Z)+"';\n",n.length=0,p||(p=g.variable,a="with ("+p+" || {}) {\n"+a+"\n}\n"),a="function("+p+") {\n"+"var __p"+(e?", __t":"")+(d?", __j = Array.prototype.join;\nfunction print() { __p += __j.call(arguments, '') }\n":";\n")+a+"return __p\n}",o&&(a+="\n//@ sourceURL=/lodash/template/source["+l++ +"]"),f=Function("_","return "+a)(N),b?f(b):(f.source=a,f)}function cG(a,b,c){var d=-1;if(c)while(++d/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:"obj"};var P=cF("var index, result<% if (init) { %> = <%= init %><% } %>;\n<%= exit %>;\n<%= top %>;\n<% if (arrayBranch) { %>var length = <%= firstArg %>.length; index = -1; <% if (objectBranch) { %>\nif (length === length >>> 0) {<% } %>\n <%= arrayBranch.beforeLoop %>;\n while (<%= arrayBranch.loopExp %>) {\n <%= arrayBranch.inLoop %>;\n } <% if (objectBranch) { %>\n}\n<% }}if (objectBranch) { if (arrayBranch) { %>else {\n<% } if (!hasDontEnumBug) { %> var skipProto = typeof <%= iteratedObject %> == 'function';\n<% } %> <%= objectBranch.beforeLoop %>;\n for (<%= objectBranch.loopExp %>) { \n<% if (hasDontEnumBug) { if (useHas) { %> if (<%= hasExp %>) {\n <% } %> <%= objectBranch.inLoop %>;<% if (useHas) { %>\n }<% } } else { %> if (!(skipProto && index == 'prototype')<% if (useHas) { %> && <%= hasExp %><% } %>) {\n <%= objectBranch.inLoop %>;\n } <% } %>\n } <% if (hasDontEnumBug) { %>\n var ctor = <%= iteratedObject %>.constructor;\n <% for (var k = 0; k < 7; k++) { %>\n index = '<%= shadowed[k] %>';\n if (<% if (shadowed[k] == 'constructor') { %>!(ctor && ctor.prototype === <%= iteratedObject %>) && <% } %><%= hasExp %>) {\n <%= objectBranch.inLoop %>;\n }<% } } if (arrayBranch) { %>\n}<% }} %>\n<%= bottom %>;\nreturn result"),Q={args:"collection, callback, thisArg",init:"collection",top:"if (!callback) {\n callback = identity\n}\nelse if (thisArg) {\n callback = iteratorBind(callback, thisArg)\n}",inLoop:"callback(collection[index], index, collection)"},R={init:"true",inLoop:"if (!callback(collection[index], index, collection)) return !result"},S={args:"object",init:"object",top:"for (var source, sourceIndex = 1, length = arguments.length; sourceIndex < length; sourceIndex++) {\n source = arguments[sourceIndex];\n"+(d?" if (source) {":""),loopExp:"index in source",useHas:!1,inLoop:"object[index] = source[index]",bottom:(d?" }\n":"")+"}"},T={init:"[]",inLoop:"callback(collection[index], index, collection) && result.push(collection[index])"},U={top:"if (thisArg) callback = iteratorBind(callback, thisArg)"},V={inLoop:{object:Q.inLoop}},W={init:"",exit:"if (!collection) return []",beforeLoop:{array:"result = Array(length)",object:"result = []"},inLoop:{array:"result[index] = callback(collection[index], index, collection)",object:"result.push(callback(collection[index], index, collection))"}},bc=X({args:"object",exit:"if (!objectTypes[typeof object] || object === null) throw TypeError()",init:"[]",inLoop:"result.push(index)"}),bg=X(W,{args:"collection, callback",inLoop:{array:"result[index] = {\n criteria: callback(collection[index], index, collection),\n value: collection[index]\n}",object:"result.push({\n criteria: callback(collection[index], index, collection),\n value: collection[index]\n})"}}),bh=X({args:"collection, target",init:"false",inLoop:"if (collection[index] === target) return true"}),bi=X(Q,R),bj=X(Q,T),bk=X(Q,U,{init:"",inLoop:"if (callback(collection[index], index, collection)) return collection[index]"}),bl=X(Q,U),bm=X(Q,{init:"{}",top:"var prop, isFunc = typeof callback == 'function';\nif (isFunc && thisArg) callback = iteratorBind(callback, thisArg)",inLoop:"prop = isFunc\n ? callback(collection[index], index, collection)\n : collection[index][callback];\n(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(collection[index])"}),bn=X(W,{args:"collection, methodName",top:"var args = slice.call(arguments, 2),\n isFunc = typeof methodName == 'function'",inLoop:{array:"result[index] = (isFunc ? methodName : collection[index][methodName]).apply(collection[index], args)",object:"result.push((isFunc ? methodName : collection[index][methodName]).apply(collection[index], args))"}}),bo=X(Q,W),bp=X(W,{args:"collection, property",inLoop:{array:"result[index] = collection[index][property]",object:"result.push(collection[index][property])"}}),bq=X({args:"collection, callback, accumulator, thisArg",init:"accumulator",top:"var noaccum = arguments.length < 3;\nif (thisArg) callback = iteratorBind(callback, thisArg)",beforeLoop:{array:"if (noaccum) result = collection[++index]"},inLoop:{array:"result = callback(result, collection[index], index, collection)",object:"result = noaccum\n ? (noaccum = false, collection[index])\n : callback(result, collection[index], index, collection)"}}),bs=X(Q,T,{inLoop:"!"+T.inLoop}),bt=X(Q,R,{init:"false",inLoop:R.inLoop.replace("!","")}),ca=X(S,{inLoop:"if (object[index] == null)"+S.inLoop}),cb=X(S),cc=X(Q,U,V,{useHas:!1}),cd=X(Q,U,V),ce=X({args:"object",init:"[]",useHas:!1,inLoop:"if (toString.call(object[index]) == funcClass) result.push(index)",bottom:"result.sort()"}),cg=function(a){return G.call(a)=="[object Arguments]"};cg(arguments)||(cg=function(a){return!!a&&!!D.call(a,"callee")});var ch=I||function(a){return G.call(a)==t},cl=X({args:"value",init:"true",top:"var className = toString.call(value);\nif (className == arrayClass || className == stringClass) return !value.length",inLoop:{object:"return false"}}),cw=K?function(a){return typeof a=="function"?bc(a):K(a)}:bc,cz=X({args:"object",init:"[]",inLoop:"result.push(object[index])"});N.VERSION="0.3.2",N.after=bP,N.bind=bQ,N.bindAll=bR,N.chain=cI,N.clone=b_,N.compact=bw,N.compose=bS,N.contains=bh,N.debounce=bT,N.defaults=ca,N.defer=bV,N.delay=bU,N.difference=bx,N.escape=cA,N.every=bi,N.extend=cb,N.filter=bj,N.find=bk,N.first=by,N.flatten=bz,N.forEach=bl,N.forIn=cc,N.forOwn=cd,N.functions=ce,N.groupBy=bm,N.has=cf,N.identity=cB,N.indexOf=bA,N.initial=bB,N.intersection=bC,N.invoke=bn,N.isArguments=cg,N.isArray=ch,N.isBoolean=ci,N.isDate=cj,N.isElement=ck,N.isEmpty=cl,N.isEqual=cm,N.isFinite=cn,N.isFunction=co,N.isNaN=cq,N.isNull=cr,N.isNumber=cs,N.isObject=cp,N.isRegExp=ct,N.isString=cu,N.isUndefined=cv,N.keys=cw,N.last=bD,N.lastIndexOf=bE,N.map=bo,N.max=bF,N.memoize=bW,N.min=bG,N.mixin=cC,N.noConflict=cD,N.once=bX,N.partial=bY,N.pick=cx,N.pluck=bp,N.range=bH,N.reduce=bq,N.reduceRight=br,N.reject=bs,N.rest=bI,N.result=cE,N.shuffle=bJ,N.size=cy,N.some=bt,N.sortBy=bu,N.sortedIndex=bK,N.tap=cJ,N.template=cF,N.throttle=bZ,N.times=cG,N.toArray=bv,N.union=bL,N.uniq=bM,N.uniqueId=cH,N.values=cz,N.without=bN,N.wrap=b$,N.zip=bO,N.all=bi,N.any=bt,N.collect=bo,N.detect=bk,N.each=bl,N.foldl=bq,N.foldr=br,N.head=by,N.include=bh,N.inject=bq,N.methods=ce,N.select=bj,N.tail=bI,N.take=by,N.unique=bM,N._iteratorTemplate=P,N._shimKeys=bc,O.prototype=N.prototype,cC(N),O.prototype.chain=cK,O.prototype.value=cL,bl(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=A[a];O.prototype[a]=function(){var a=this._wrapped;return b.apply(a,arguments),a.length===0&&delete a[0],this._chain&&(a=new O(a),a._chain=!0),a}}),bl(["concat","join","slice"],function(a){var b=A[a];O.prototype[a]=function(){var a=this._wrapped,c=b.apply(a,arguments);return this._chain&&(c=new O(c),c._chain=!0),c}}),typeof define=="function"&&typeof define.amd=="object"&&define.amd?(a._=N,define(function(){return N})):c?typeof module=="object"&&module&&module.exports==c?(module.exports=N)._=N:c._=N:a._=N}(this);var JSON;JSON||(JSON={}),function(){function f(a){return a<10?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c1?h.call(arguments,0):c,--f||i.resolveWith(i,b)}}function m(a){return function(b){e[a]=arguments.length>1?h.call(arguments,0):b,i.notifyWith(k,e)}}var b=h.call(arguments,0),c=0,d=b.length,e=new Array(d),f=d,g=d,i=d<=1&&a&&j(a.promise)?a:n.Deferred(),k=i.promise();if(d>1){for(;cb?1:0},numeric:function(a){return _.isNaN(Number(a))?null:Number(a)}},string:{name:"string",coerce:function(a){return a==null?null:a.toString()},test:function(a){return a===null||typeof a=="undefined"||typeof a=="string"},compare:function(a,b){return a==null&&b!=null?-1:a!=null&&b==null?1:ab?1:0},numeric:function(a){return _.isNaN(+a)||a===null?null:_.isNumber(+a)?+a:null}},"boolean":{name:"boolean",regexp:/^(true|false)$/,coerce:function(a){return a==="false"?!1:Boolean(a)},test:function(a){return a===null||typeof a=="undefined"||typeof a=="boolean"||this.regexp.test(a)?!0:!1},compare:function(a,b){return a==null&&b!=null?-1:a!=null&&b==null?1:a==null&&b==null?0:a===b?0:ab?1:0},numeric:function(a){return a.valueOf()}}}}(this,_),function(a,_){var b=a.Miso||(a.Miso={});b.Event=function(a){_.isArray(a)||(a=[a]),this.deltas=a},_.extend(b.Event.prototype,{affectedColumns:function(){var a=[];return _.each(this.deltas,function(b){b.old=b.old||[],b.changed=b.changed||[],a=_.chain(a).union(_.keys(b.old),_.keys(b.changed)).reject(function(a){return a==="_id"}).value()}),a}}),_.extend(b.Event,{isRemove:function(a){return _.isUndefined(a.changed)||_.keys(a.changed).length===0?!0:!1},isAdd:function(a){return _.isUndefined(a.old)||_.keys(a.old).length===0?!0:!1},isUpdate:function(a){return!this.isRemove(a)&&!this.isAdd(a)?!0:!1}}),b.Events={},b.Events.bind=function(a,b,c){var d=this._callbacks||(this._callbacks={}),e=d[a]||(d[a]={}),f=e.tail||(e.tail=e.next={});return f.callback=b,f.context=c,e.tail=f.next={},this},b.Events.unbind=function(a,b){var c,d,e;if(!a)this._callbacks=null;else if(c=this._callbacks)if(!b)c[a]={};else if(d=c[a])while((e=d)&&(d=d.next)){if(d.callback!==b)continue;e.next=d.next,d.context=d.callback=null;break}return this},b.Events.trigger=function(a){var b,c,d,e,f,g=["all",a];if(!(c=this._callbacks))return this;while(f=g.pop()){if(!(b=c[f]))continue;e=f==="all"?arguments:Array.prototype.slice.call(arguments,1);while(b=b.next)(d=b.callback)&&d.apply(b.context||this,e)}return this},b.Events._buildEvent=function(a){return new b.Event(a)}}(this,_),function(a,_){var b=a.Miso||{};b.Builder={detectColumnType:function(a,c){var d=_.inject(c.slice(0,5),function(a,c){var d=b.typeOf(c);return c!==""&&a.indexOf(d)===-1&&!_.isNull(c)&&a.push(d),a},[]);return d.length===1?a.type=d[0]:a.type="mixed",a},detectColumnTypes:function(a,c){_.each(c,function(c,d){var e=a.column(d);if(e.type){e.force=!0;return}b.Builder.detectColumnType(e,c)},this)},cacheRows:function(a){b.Builder.clearRowCache(a),_.each(a._columns[a._columnPositionByName._id].data,function(b,c){a._rowPositionById[b]=c,a._rowIdByPosition.push(b)},a);var c=_.uniq(_.map(a._columns,function(a){return a.data.length}));if(c.length>1)throw new Error("Row lengths need to be the same. Empty values should be set to null."+_.map(a._columns,function(a){return a.data+"|||"}));a.length=c[0]},clearRowCache:function(a){a._rowPositionById={},a._rowIdByPosition=[]},cacheColumns:function(a){a._columnPositionByName={},_.each(a._columns,function(b,c){a._columnPositionByName[b.name]=c})}},Array.prototype.indexOf||(Array.prototype.indexOf=function(a,b){for(var c=b||0,d=this.length;c0&&(a=this.numericAt(c));return b.types[this.type].coerce(a,this)},_min:function(){var a=Infinity;for(var c=0;c=0;c--)a.apply(b||this,[this.rowByPosition(c),c])},eachColumn:function(a,b){var c=this.columnNames();for(var d=0;d=0)f(b,a-1),b--};if(c>2){g(c),d=c-1;while(d>1)e(d,0),d--,f(0,d)}else this.comparator(this.rowByPosition(0),this.rowByPosition(1))>0&&e(0,1);return this.comparator(this.rowByPosition(this.length-2),this.rowByPosition(this.length-1))>0&&e(this.length-1,this.length-2),this.syncable&&b.silent&&this.trigger("sort"),this},toJSON:function(){var a=[];for(var b=0;b0&&_.times(a,function(){b.push(_.uniqueId())}),this.addColumn({name:"_id",type:"number",data:b});if(this._columnPositionByName._id!==0){var c=this._columns[this._columnPositionByName._id],d=this._columnPositionByName._id;this._columns.splice(d,1),this._columns.unshift(c),this._columnPositionByName._id=0,_.each(this._columnPositionByName,function(a,b){b!=="_id"&&this._columnPositionByName[b]1&&(f=b),a},{}),new Error('You have more than one column named "'+f+'"')}_.each(a.table.rows,function(a){a=a.c;for(e=0;e0){var n=0,o=0,p=d.length;while(n70?1900:2e3);break;case"YYYY":c[0]=~~Math.abs(b);break;case"a":case"A":d.isPm=(b+"").toLowerCase()==="pm";break;case"H":case"HH":case"h":case"hh":c[3]=~~b;break;case"m":case"mm":c[4]=~~b;break;case"s":case"ss":c[5]=~~b;break;case"S":case"SS":case"SSS":c[6]=~~(("0."+b)*1e3);break;case"Z":case"ZZ":d.isUTC=!0,e=(b+"").match(y),e&&e[1]&&(d.tzh=~~e[1]),e&&e[2]&&(d.tzm=~~e[2]),e&&e[0]==="+"&&(d.tzh=-d.tzh,d.tzm=-d.tzm)}}function W(a,b){var c=[0,0,1,0,0,0,0],d={tzh:0,tzm:0},e=b.match(k),f,g;for(f=0;f0,j[4]=c,Z.apply({},j)}function _(a,b){moment.fn[a]=function(a){var c=this._isUTC?"UTC":"";return a!=null?(this._d["set"+c+b](a),this):this._d["get"+c+b]()}}function ba(a){moment.duration.fn[a]=function(){return this._data[a]}}function bb(a,b){moment.duration.fn["as"+a]=function(){return+this/b}}var moment,c="1.7.0",d=Math.round,e,f={},g="en",h=typeof module!="undefined"&&module.exports,i="months|monthsShort|weekdays|weekdaysShort|weekdaysMin|longDateFormat|calendar|relativeTime|ordinal|meridiem".split("|"),j=/^\/?Date\((\-?\d+)/i,k=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?)/g,l=/(LT|LL?L?L?)/g,m=/(^\[)|(\\)|\]$/g,n=/([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi,o=/\d\d?/,p=/\d{1,3}/,q=/\d{3}/,r=/\d{1,4}/,s=/[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i,t=/Z|[\+\-]\d\d:?\d\d/i,u=/T/i,v=/^\s*\d{4}-\d\d-\d\d(T(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,w="YYYY-MM-DDTHH:mm:ssZ",x=[["HH:mm:ss.S",/T\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/T\d\d:\d\d:\d\d/],["HH:mm",/T\d\d:\d\d/],["HH",/T\d\d/]],y=/([\+\-]|\d\d)/gi,z="Month|Date|Hours|Minutes|Seconds|Milliseconds".split("|"),A={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},B={},C={M:"(a=t.month()+1)",MMM:'v("monthsShort",t.month())',MMMM:'v("months",t.month())',D:"(a=t.date())",DDD:"(a=new Date(t.year(),t.month(),t.date()),b=new Date(t.year(),0,1),a=~~(((a-b)/864e5)+1.5))",d:"(a=t.day())",dd:'v("weekdaysMin",t.day())',ddd:'v("weekdaysShort",t.day())',dddd:'v("weekdays",t.day())',w:"(a=new Date(t.year(),t.month(),t.date()-t.day()+5),b=new Date(a.getFullYear(),0,4),a=~~((a-b)/864e5/7+1.5))",YY:"p(t.year()%100,2)",YYYY:"p(t.year(),4)",a:"m(t.hours(),t.minutes(),!0)",A:"m(t.hours(),t.minutes(),!1)",H:"t.hours()",h:"t.hours()%12||12",m:"t.minutes()",s:"t.seconds()",S:"~~(t.milliseconds()/100)",SS:"p(~~(t.milliseconds()/10),2)",SSS:"p(t.milliseconds(),3)",Z:'((a=-t.zone())<0?((a=-a),"-"):"+")+p(~~(a/60),2)+":"+p(~~a%60,2)',ZZ:'((a=-t.zone())<0?((a=-a),"-"):"+")+p(~~(10*a/6),4)'},D="DDD w M D d".split(" "),E="M D H h m s w".split(" ");while(D.length)e=D.pop(),C[e+"o"]=C[e]+"+o(a)";while(E.length)e=E.pop(),C[e+e]="p("+C[e]+",2)";C.DDDD="p("+C.DDD+",3)",moment=function(c,d){if(c===null||c==="")return null;var e,f;return moment.isMoment(c)?new F(new a(+c._d),c._isUTC,c._lang):(d?K(d)?e=X(c,d):e=W(c,d):(f=j.exec(c),e=c===b?new a:f?new a(+f[1]):c instanceof a?c:K(c)?M(c):typeof c=="string"?Y(c):new a(c)),new F(e))},moment.utc=function(a,b){return K(a)?new F(M(a,!0),!0):(typeof a=="string"&&!t.exec(a)&&(a+=" +0000",b&&(b+=" Z")),moment(a,b).utc())},moment.unix=function(a){return moment(a*1e3)},moment.duration=function(a,b){var c=moment.isDuration(a),d=typeof a=="number",e=c?a._data:d?{}:a,f;return d&&(b?e[b]=a:e.milliseconds=a),f=new G(e),c&&(f._lang=a._lang),f},moment.humanizeDuration=function(a,b,c){return moment.duration(a,b===!0?null:b).humanize(b===!0?!0:c)},moment.version=c,moment.defaultFormat=w,moment.lang=function(a,b){var c;if(!a)return g;(b||!f[a])&&N(a,b);if(f[a]){for(c=0;c11?c?"pm":"PM":c?"am":"AM"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinal:function(a){var b=a%10;return~~(a%100/10)===1?"th":b===1?"st":b===2?"nd":b===3?"rd":"th"}}),moment.fn=F.prototype={clone:function(){return moment(this)},valueOf:function(){return+this._d},unix:function(){return Math.floor(+this._d/1e3)},toString:function(){return this._d.toString()},toDate:function(){return this._d},toArray:function(){var a=this;return[a.year(),a.month(),a.date(),a.hours(),a.minutes(),a.seconds(),a.milliseconds(),!!this._isUTC]},isValid:function(){return this._a?!L(this._a,(this._a[7]?moment.utc(this):this).toArray()):!isNaN(this._d.getTime())},utc:function(){return this._isUTC=!0,this},local:function(){return this._isUTC=!1,this},format:function(a){return T(this,a?a:moment.defaultFormat)},add:function(a,b){var c=b?moment.duration(+b,a):moment.duration(a);return J(this,c,1),this},subtract:function(a,b){var c=b?moment.duration(+b,a):moment.duration(a);return J(this,c,-1),this},diff:function(a,b,c){var e=this._isUTC?moment(a).utc():moment(a).local(),f=(this.zone()-e.zone())*6e4,g=this._d-e._d-f,h=this.year()-e.year(),i=this.month()-e.month(),j=this.date()-e.date(),k;return b==="months"?k=h*12+i+j/30:b==="years"?k=h+(i+j/30)/12:k=b==="seconds"?g/1e3:b==="minutes"?g/6e4:b==="hours"?g/36e5:b==="days"?g/864e5:b==="weeks"?g/6048e5:g,c?k:d(k)},from:function(a,b){return moment.duration(this.diff(a)).lang(this._lang).humanize(!b)},fromNow:function(a){return this.from(moment(),a)},calendar:function(){var a=this.diff(moment().sod(),"days",!0),b=this.lang().calendar,c=b.sameElse,d=a<-6?c:a<-1?b.lastWeek:a<0?b.lastDay:a<1?b.sameDay:a<2?b.nextDay:a<7?b.nextWeek:c;return this.format(typeof d=="function"?d.apply(this):d)},isLeapYear:function(){var a=this.year();return a%4===0&&a%100!==0||a%400===0},isDST:function(){return this.zone()=(c||n),f=e?{}:a;if(e){var g,h=b-1;while(++h-1}return cM(f,a,b)>-1}}function bD(){var a,b,c,d=-1,e=arguments.length,f={bottom:"",exit:"",init:"",top:"",arrayBranch:{beforeLoop:""},objectBranch:{beforeLoop:""}};while(++dc?1:d";var c=D.length;return D[c]="' +\n__e("+b+") +\n'",C+c}function bL(a,b,c,d){if(d){var e=D.length;return D[e]="';\n"+d+";\n__p += '",C+e}return b?bK(null,b):bM(null,c)}function bM(a,b){if(a&&p.test(b))return"";var c=D.length;return D[c]="' +\n((__t = ("+b+")) == null ? '' : __t) +\n'",C+c}function bN(a){return bn[a]}function bO(a){return J.call(a)==O}function bQ(a){return typeof a=="function"}function bR(a,b){return a?a==j||a.__proto__==j&&(b||!bO(a)):!1}function bT(a,b,c,d,e){if(a==null)return a;c&&(b=!1),e||(e={value:null}),e.value==null&&(e.value=!!(i.clone||k.clone||l.clone));var f=bo[typeof a];if((f||e.value)&&a.clone&&bQ(a.clone))return e.value=null,a.clone(b);if(f){var g=J.call(a);if(!bl[g]||bb&&bO(a))return a;var h=g==P;f=h||(g==U?bR(a,!0):f)}if(!f||!b)return f?h?I.call(a):bW({},a):a;var j=a.constructor;switch(g){case Q:return new j(a==!0);case R:return new j(+a);case T:case W:return new j(a);case V:return j(a.source,u.exec(a))}d||(d=[]);var m=d.length;while(m--)if(d[m].source==a)return d[m].value;m=a.length;var n=h?j(m):{};d.push({value:n,source:a});if(h){var o=-1;while(++o-1&&c===c>>>0&&bQ(a.splice)?c:cm(a).length}function cC(a,b,c,d){if(!a)return c;var e=a.length,f=arguments.length<3;d&&(b=bI(b,d));if(e>-1&&e===e>>>0){var g=bd&&J.call(a)==W?a.split(""):a;e&&f&&(c=g[--e]);while(e--)c=b(c,g[e],e,a);return c}var h,i=cm(a);e=i.length,e&&f&&(c=a[i[--e]]);while(e--)h=i[e],c=b(c,a[h],h,a);return c}function cG(a){if(!a)return[];if(a.toArray&&bQ(a.toArray))return a.toArray();var b=a.length;return b>-1&&b===b>>>0?(bc?J.call(a)==W:typeof a=="string")?a.split(""):I.call(a):cq(a)}function cI(a){var b=[];if(!a)return b;var c=-1,d=a.length;while(++ce&&(e=a[g]);return e}c&&(b=bI(b,c));while(++gd&&(d=f,e=a[g]);return e}function cS(a,b,c){var d=Infinity,e=d;if(!a)return e;var f,g=-1,h=a.length;if(!b){while(++g>>1,c(a[e])>>1,a[e]2)return K.call.apply(K,arguments);var e=I.call(arguments,2);return f}function dd(){var a=arguments;return function(){var b=arguments,c=a.length;while(c--)b=[a[c].apply(this,b)];return b[0]}}function de(a,b,c){function h(){g=null,c||a.apply(f,d)}var d,e,f,g;return function(){var i=c&&!g;return d=arguments,f=this,X(g),g=Y(h,b),i&&(e=a.apply(f,d)),e}}function df(a,c){var d=I.call(arguments,2);return Y(function(){return a.apply(b,d)},c)}function dg(a){var c=I.call(arguments,1);return Y(function(){return a.apply(b,c)},1)}function dh(a,b){var c={};return function(){var d=b?b.apply(this,arguments):arguments[0];return F.call(c,d)?c[d]:c[d]=a.apply(this,arguments)}}function di(a){var b,c=!1;return function(){return c?b:(c=!0,b=a.apply(this,arguments),a=null,b)}}function dj(a){var b=I.call(arguments,1),c=b.length;return function(){var d,e=arguments;return e.length&&(b.length=c,G.apply(b,e)),d=b.length==1?a.call(this,b[0]):a.apply(this,b),b.length=c,d}}function dk(a,b){function h(){g=new Date,f=null,a.apply(e,c)}var c,d,e,f,g=0;return function(){var i=new Date,j=b-(i-g);return c=arguments,e=this,j<=0?(g=i,d=a.apply(e,c)):f||(f=Y(h,j)),d}}function dl(a,b){return function(){var c=[a];return arguments.length&&G.apply(c,arguments),b.apply(this,c)}}function dm(a){return a==null?"":(a+"").replace(y,bH)}function dn(a){return a}function dp(a){cw(bZ(a),function(b){var c=bq[b]=a[b];br.prototype[b]=function(){var a=[this._wrapped];arguments.length&&G.apply(a,arguments);var b=c.apply(bq,a);return this._chain&&(b=new br(b),b._chain=!0),b}})}function dq(){return a._=o,this}function dr(a,b){if(!a)return null;var c=a[b];return bQ(c)?a[b]():c}function ds(a,b,g){g||(g={}),a+="";var h,i,j=g.escape,k=g.evaluate,l=g.interpolate,m=bq.templateSettings,n=g.variable||m.variable,o=n;j==null&&(j=m.escape),k==null&&(k=m.evaluate||!1),l==null&&(l=m.interpolate),j&&(a=a.replace(j,bK)),l&&(a=a.replace(l,bM)),k!=c&&(c=k,f=RegExp("|"+(k?"|"+k.source:""),"g")),h=D.length,a=a.replace(f,bL),h=h!=D.length,a="__p += '"+a.replace(z,bG).replace(x,bF)+"';\n",D.length=0,o||(n=d||"obj",h?a="with ("+n+") {\n"+a+"\n}\n":(n!=d&&(d=n,e=RegExp("(\\(\\s*)"+n+"\\."+n+"\\b","g")),a=a.replace(v,"$&"+n+".").replace(e,"$1__d"))),a=(h?a.replace(r,""):a).replace(s,"$1").replace(t,"$1;"),a="function("+n+") {\n"+(o?"":n+" || ("+n+" = {});\n")+"var __t, __p = '', __e = _.escape"+(h?", __j = Array.prototype.join;\nfunction print() { __p += __j.call(arguments, '') }\n":(o?"":", __d = "+n+"."+n+" || "+n)+";\n")+a+"return __p\n}",bj&&(a+="\n//@ sourceURL=/lodash/template/source["+B++ +"]");try{i=Function("_","return "+a)(bq)}catch(p){i=function(){throw p}}return b?i(b):(i.source=a,i)}function dt(a,b,c){var d=-1;if(c)while(++d|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/,q=/&(?:amp|lt|gt|quot|#x27);/g,r=/\b__p \+= '';/g,s=/\b(__p \+=) '' \+/g,t=/(__e\(.*?\)|\b__t\)) \+\n'';/g,u=/\w*$/,v=/(?:__e|__t = )\(\s*(?![\d\s"']|this\.)/g,w=RegExp("^"+(j.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),x=/__token__(\d+)/g,y=/[&<>"']/g,z=/['\n\r\t\u2028\u2029\\]/g,A=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],B=0,C="__token__",D=[],E=h.concat,F=j.hasOwnProperty,G=h.push,H=j.propertyIsEnumerable,I=h.slice,J=j.toString,K=w.test(K=I.bind)&&K,L=w.test(L=Array.isArray)&&L,M=a.isFinite,N=w.test(N=Object.keys)&&N,O="[object Arguments]",P="[object Array]",Q="[object Boolean]",R="[object Date]",S="[object Function]",T="[object Number]",U="[object Object]",V="[object RegExp]",W="[object String]",X=a.clearTimeout,Y=a.setTimeout,Z,$,_,ba=!0;(function(){function c(){this.x=1}var a={0:1,length:1},b=[];c.prototype={valueOf:1,y:1};for(var d in new c)b.push(d);for(d in arguments)ba=!d;Z=(b+"").length<4,_=b[0]!="x",$=(b.splice.call(a,0,1),a[0])})(1);var bb=!bO(arguments),bc=I.call("x")[0]!="x",bd="x"[0]+Object("x")[0]!="xx";try{var be=({toString:0}+"",J.call(a.document||0)==U)}catch(bf){}var bg=K&&/\n|Opera/.test(K+J.call(a.opera)),bh=N&&/^.+$|true/.test(N+!!a.attachEvent),bi=!bg;try{var bj=(Function("//@")(),!a.attachEvent)}catch(bf){}var bk={};bk[Q]=bk[R]=bk[S]=bk[T]=bk[U]=bk[V]=!1,bk[O]=bk[P]=bk[W]=!0;var bl={};bl[O]=bl[S]=!1,bl[P]=bl[Q]=bl[R]=bl[T]=bl[U]=bl[V]=bl[W]=!0;var bm={"&":"&","<":"<",">":">",'"':""","'":"'"},bn={"&":"&","<":"<",">":">",""":'"',"'":"'"},bo={"boolean":!1,"function":!0,object:!0,number:!1,string:!1,"undefined":!1,unknown:!0},bp={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"};bq.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:""};var bs=ds("<% if (useStrict) { %>'use strict';\n<% } %>var index, value, iteratee = <%= firstArg %>, result<% if (init) { %> = <%= init %><% } %>;\n<%= exit %>;\n<%= top %>;\n<% if (arrayBranch) { %>var length = iteratee.length; index = -1; <% if (objectBranch) { %>\nif (length > -1 && length === length >>> 0) {<% } %> <% if (noCharByIndex) { %>\n if (toString.call(iteratee) == stringClass) {\n iteratee = iteratee.split('')\n } <% } %>\n <%= arrayBranch.beforeLoop %>;\n while (++index < length) {\n value = iteratee[index];\n <%= arrayBranch.inLoop %>\n } <% if (objectBranch) { %>\n}<% } %><% } %><% if (objectBranch) { %> <% if (arrayBranch) { %>\nelse { <% } else if (noArgsEnum) { %>\n var length = iteratee.length; index = -1;\n if (length && isArguments(iteratee)) {\n while (++index < length) {\n value = iteratee[index += ''];\n <%= objectBranch.inLoop %>\n }\n } else { <% } %> <% if (!hasDontEnumBug) { %>\n var skipProto = typeof iteratee == 'function' && \n propertyIsEnumerable.call(iteratee, 'prototype');\n <% } %> <% if (isKeysFast && useHas) { %>\n var ownIndex = -1,\n ownProps = objectTypes[typeof iteratee] ? nativeKeys(iteratee) : [],\n length = ownProps.length;\n\n <%= objectBranch.beforeLoop %>;\n while (++ownIndex < length) {\n index = ownProps[ownIndex];\n <% if (!hasDontEnumBug) { %>if (!(skipProto && index == 'prototype')) {\n <% } %> value = iteratee[index];\n <%= objectBranch.inLoop %>\n <% if (!hasDontEnumBug) { %>}\n<% } %> } <% } else { %>\n <%= objectBranch.beforeLoop %>;\n for (index in iteratee) { <% if (!hasDontEnumBug || useHas) { %>\n if (<% if (!hasDontEnumBug) { %>!(skipProto && index == 'prototype')<% } if (!hasDontEnumBug && useHas) { %> && <% } if (useHas) { %>hasOwnProperty.call(iteratee, index)<% } %>) { <% } %>\n value = iteratee[index];\n <%= objectBranch.inLoop %>;\n <% if (!hasDontEnumBug || useHas) { %>}\n<% } %> } <% } %> <% if (hasDontEnumBug) { %>\n\n var ctor = iteratee.constructor;\n <% for (var k = 0; k < 7; k++) { %>\n index = '<%= shadowed[k] %>';\n if (<% if (shadowed[k] == 'constructor') { %>!(ctor && ctor.prototype === iteratee) && <% } %>hasOwnProperty.call(iteratee, index)) {\n value = iteratee[index];\n <%= objectBranch.inLoop %>\n } <% } %> <% } %> <% if (arrayBranch || noArgsEnum) { %>\n}<% } %><% } %>\n<%= bottom %>;\nreturn result"),bt={args:"collection, callback, thisArg",init:"collection",top:"if (!callback) {\n callback = identity\n}\nelse if (thisArg) {\n callback = iteratorBind(callback, thisArg)\n}",inLoop:"if (callback(value, index, collection) === false) return result"},bu={init:"{}",top:"var prop;\nif (typeof callback != 'function') {\n var valueProp = callback;\n callback = function(value) { return value[valueProp] }\n}\nelse if (thisArg) {\n callback = iteratorBind(callback, thisArg)\n}",inLoop:"prop = callback(value, index, collection);\n(hasOwnProperty.call(result, prop) ? result[prop]++ : result[prop] = 1)"},bv={useHas:!1,args:"object, callback, thisArg",init:"{}",top:"var isFunc = typeof callback == 'function';\nif (!isFunc) {\n var props = concat.apply(ArrayProto, arguments)\n} else if (thisArg) {\n callback = iteratorBind(callback, thisArg)\n}",inLoop:"if (isFunc\n ? !callback(value, index, object)\n : indexOf(props, index) < 0\n) result[index] = value"},bw={init:"true",inLoop:"if (!callback(value, index, collection)) return !result"},bx={useHas:!1,useStrict:!1,args:"object",init:"object",top:"for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {\n if (iteratee = arguments[argsIndex]) {",inLoop:"result[index] = value",bottom:" }\n}"},by={init:"[]",inLoop:"callback(value, index, collection) && result.push(value)"},bz={top:"if (thisArg) callback = iteratorBind(callback, thisArg)"},bA={inLoop:{object:bt.inLoop}},bB={init:"",exit:"if (!collection) return []",beforeLoop:{array:"result = Array(length)",object:"result = "+(bh?"Array(length)":"[]")},inLoop:{array:"result[index] = callback(value, index, collection)",object:"result"+(bh?"[ownIndex] = ":".push")+"(callback(value, index, collection))"}};bb&&(bO=function(a){return!!a&&!!F.call(a,"callee")});var bP=L||function(a){return J.call(a)==P};bQ(/x/)&&(bQ=function(a){return J.call(a)==S}),bR(bo)||(bR=function(a,b){var c=!1;if(!a||typeof a!="object"||!b&&bO(a))return c;var d=a.constructor;return(!be||typeof a.toString=="function"||typeof (a+"")!="string")&&(!bQ(d)||d instanceof d)?_?(bX(a,function(b,d){return c=!F.call(a,d),!1}),c===!1):(bX(a,function(a,b){c=b}),c===!1||F.call(a,c)):c});var bS=bD({args:"object",init:"[]",inLoop:"result.push(index)"}),bU=bD(bx,{inLoop:"if (result[index] == null) "+bx.inLoop}),bV=bD(bv),bW=bD(bx),bX=bD(bt,bz,bA,{useHas:!1}),bY=bD(bt,bz,bA),bZ=bD({useHas:!1,args:"object",init:"[]",inLoop:"if (isFunction(value)) result.push(index)",bottom:"result.sort()"}),cc=bD({args:"value",init:"true",top:"var className = toString.call(value),\n length = value.length;\nif (arrayLikeClasses[className]"+(bb?" || isArguments(value)":"")+" ||\n"+" (className == objectClass && length > -1 && length === length >>> 0 &&\n"+" isFunction(value.splice))"+") return !length",inLoop:{object:"return false"}}),cm=N?function(a){var b=typeof a;return b=="function"&&H.call(a,"prototype")?bS(a):a&&bo[b]?N(a):[]}:bS,cn=bD(bx,{args:"object, source, indicator, stack",top:"var destValue, found, isArr, stackLength, recursive = indicator == isPlainObject;\nif (!recursive) stack = [];\nfor (var argsIndex = 1, argsLength = recursive ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n if (iteratee = arguments[argsIndex]) {",inLoop:"if (value && ((isArr = isArray(value)) || isPlainObject(value))) {\n found = false; stackLength = stack.length;\n while (stackLength--) {\n if (found = stack[stackLength].source == value) break\n }\n if (found) {\n result[index] = stack[stackLength].value\n } else {\n destValue = (destValue = result[index]) && isArr\n ? (isArray(destValue) ? destValue : [])\n : (isPlainObject(destValue) ? destValue : {});\n stack.push({ value: destValue, source: value });\n result[index] = callee(destValue, value, isPlainObject, stack)\n }\n} else if (value != null) {\n result[index] = value\n}"}),co=bD(bv,{top:"if (typeof callback != 'function') {\n var prop,\n props = concat.apply(ArrayProto, arguments),\n length = props.length;\n for (index = 1; index < length; index++) {\n prop = props[index];\n if (prop in object) result[prop] = object[prop]\n }\n} else {\n if (thisArg) callback = iteratorBind(callback, thisArg)",inLoop:"if (callback(value, index, object)) result[index] = value",bottom:"}"}),cq=bD({args:"object",init:"[]",inLoop:"result.push(value)"}),cr=bD({args:"collection, target",init:"false",noCharByIndex:!1,beforeLoop:{array:"if (toString.call(collection) == stringClass) return collection.indexOf(target) > -1"},inLoop:"if (value === target) return true"}),cs=bD(bt,bu),ct=bD(bt,bw),cu=bD(bt,by),cv=bD(bt,bz,{init:"",inLoop:"if (callback(value, index, collection)) return value"}),cw=bD(bt,bz),cx=bD(bt,bu,{inLoop:"prop = callback(value, index, collection);\n(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(value)"}),cy=bD(bB,{args:"collection, methodName",top:"var args = slice.call(arguments, 2),\n isFunc = typeof methodName == 'function'",inLoop:{array:"result[index] = (isFunc ? methodName : value[methodName]).apply(value, args)",object:"result"+(bh?"[ownIndex] = ":".push")+"((isFunc ? methodName : value[methodName]).apply(value, args))"}}),cz=bD(bt,bB),cA=bD(bB,{args:"collection, property",inLoop:{array:"result[index] = value[property]",object:"result"+(bh?"[ownIndex] = ":".push")+"(value[property])"}}),cB=bD({args:"collection, callback, accumulator, thisArg",init:"accumulator",top:"var noaccum = arguments.length < 3;\nif (thisArg) callback = iteratorBind(callback, thisArg)",beforeLoop:{array:"if (noaccum) result = iteratee[++index]"},inLoop:{array:"result = callback(result, value, index, collection)",object:"result = noaccum\n ? (noaccum = false, value)\n : callback(result, value, index, collection)"}}),cD=bD(bt,by,{inLoop:"!"+by.inLoop}),cE=bD(bt,bw,{init:"false",inLoop:bw.inLoop.replace("!","")}),cF=bD(bt,bu,bB,{inLoop:{array:"result[index] = {\n criteria: callback(value, index, collection),\n index: index,\n value: value\n}",object:"result"+(bh?"[ownIndex] = ":".push")+"({\n"+" criteria: callback(value, index, collection),\n"+" index: index,\n"+" value: value\n"+"})"},bottom:"result.sort(compareAscending);\nlength = result.length;\nwhile (length--) {\n result[length] = result[length].value\n}"}),cH=bD(by,{args:"collection, properties",top:"var props = [];\nforIn(properties, function(value, prop) { props.push(prop) });\nvar propsLength = props.length",inLoop:"for (var prop, pass = true, propIndex = 0; propIndex < propsLength; propIndex++) {\n prop = props[propIndex];\n if (!(pass = value[prop] === properties[prop])) break\n}\npass && result.push(value)"}),dc=bD({useHas:!1,useStrict:!1,args:"object",init:"object",top:"var funcs = arguments,\n length = funcs.length;\nif (length > 1) {\n for (var index = 1; index < length; index++) {\n result[funcs[index]] = bind(result[funcs[index]], result)\n }\n return result\n}",inLoop:"if (isFunction(result[index])) {\n result[index] = bind(result[index], result)\n}"});bq.VERSION="0.6.1",bq.after=da,bq.bind=db,bq.bindAll=dc,bq.chain=dw,bq.clone=bT,bq.compact=cI,bq.compose=dd,bq.contains=cr,bq.countBy=cs,bq.debounce=de,bq.defaults=bU,bq.defer=dg,bq.delay=df,bq.difference=cJ,bq.drop=bV,bq.escape=dm,bq.every=ct,bq.extend=bW,bq.filter=cu,bq.find=cv,bq.first=cK,bq.flatten=cL,bq.forEach=cw,bq.forIn=bX,bq.forOwn=bY,bq.functions=bZ,bq.groupBy=cx,bq.has=b$,bq.identity=dn,bq.indexOf=cM,bq.initial=cN,bq.intersection=cO,bq.invoke=cy,bq.isArguments=bO,bq.isArray=bP,bq.isBoolean=b_,bq.isDate=ca,bq.isElement=cb,bq.isEmpty=cc,bq.isEqual=cd,bq.isFinite=ce,bq.isFunction=bQ,bq.isNaN=cg,bq.isNull=ch,bq.isNumber=ci,bq.isObject=cf,bq.isRegExp=cj,bq.isString=ck,bq.isUndefined=cl,bq.keys=cm,bq.last=cP,bq.lastIndexOf=cQ,bq.map=cz,bq.max=cR,bq.memoize=dh,bq.merge=cn,bq.min=cS,bq.mixin=dp,bq.noConflict=dq,bq.once=di,bq.partial=dj,bq.pick=co,bq.pluck=cA,bq.range=cT,bq.reduce=cB,bq.reduceRight=cC,bq.reject=cD,bq.rest=cU,bq.result=dr,bq.shuffle=cV,bq.size=cp,bq.some=cE,bq.sortBy=cF,bq.sortedIndex=cW,bq.tap=dx,bq.template=ds,bq.throttle=dk,bq.times=dt,bq.toArray=cG,bq.unescape=du,bq.union=cX,bq.uniq=cY,bq.uniqueId=dv,bq.values=cq,bq.where=cH,bq.without=cZ,bq.wrap=dl,bq.zip=c$,bq.zipObject=c_,bq.all=ct,bq.any=cE,bq.collect=cz,bq.detect=cv,bq.each=cw,bq.foldl=cB,bq.foldr=cC,bq.head=cK,bq.include=cr,bq.inject=cB,bq.methods=bZ,bq.omit=bV,bq.select=cu,bq.tail=cU,bq.take=cK,bq.unique=cY,bq._iteratorTemplate=bs,bq._shimKeys=bS,br.prototype=bq.prototype,dp(bq),br.prototype.chain=dy,br.prototype.value=dz,cw(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=h[a];br.prototype[a]=function(){var a=this._wrapped;return b.apply(a,arguments),$&&a.length===0&&delete a[0],this._chain&&(a=new br(a),a._chain=!0),a}}),cw(["concat","join","slice"],function(a){var b=h[a];br.prototype[a]=function(){var a=this._wrapped,c=b.apply(a,arguments);return this._chain&&(c=new br(c),c._chain=!0),c}}),typeof define=="function"&&typeof define.amd=="object"&&define.amd?(a._=bq,define(function(){return bq})):g?typeof module=="object"&&module&&module.exports==g?(module.exports=bq)._=bq:g._=bq:a._=bq}(this);var JSON;JSON||(JSON={}),function(){function f(a){return a<10?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c1?h.call(arguments,0):c,--f||i.resolveWith(i,b)}}function m(a){return function(b){e[a]=arguments.length>1?h.call(arguments,0):b,i.notifyWith(k,e)}}var b=h.call(arguments,0),c=0,d=b.length,e=new Array(d),f=d,g=d,i=d<=1&&a&&j(a.promise)?a:n.Deferred(),k=i.promise();if(d>1){for(;cb)return 1},numeric:function(a){return a===null||_.isNaN(+a)?null:+a}},string:{name:"string",coerce:function(a){return _.isNaN(a)||a===null||typeof a=="undefined"?null:a.toString()},test:function(a){return a===null||typeof a=="undefined"||typeof a=="string"},compare:function(a,b){return a==null&&b!=null?-1:a!=null&&b==null?1:ab?1:0},numeric:function(a){return _.isNaN(+a)||a===null?null:_.isNumber(+a)?+a:null}},"boolean":{name:"boolean",regexp:/^(true|false)$/,coerce:function(a){return _.isNaN(a)||a===null||typeof a=="undefined"?null:a==="false"?!1:Boolean(a)},test:function(a){return a===null||typeof a=="undefined"||typeof a=="boolean"||this.regexp.test(a)?!0:!1},compare:function(a,b){return a==null&&b!=null?-1:a!=null&&b==null?1:a==null&&b==null?0:a===b?0:ab?1:0},numeric:function(a){return _.isNaN(a)||a===null?null:a.valueOf()}}}}(this,_),function(a,_){var b=a.Miso||(a.Miso={});b.Event=function(a){_.isArray(a)||(a=[a]),this.deltas=a},_.extend(b.Event.prototype,{affectedColumns:function(){var a=[];return _.each(this.deltas,function(b){b.old=b.old||[],b.changed=b.changed||[],a=_.chain(a).union(_.keys(b.old),_.keys(b.changed)).reject(function(a){return a==="_id"}).value()}),a}}),_.extend(b.Event,{isRemove:function(a){return _.isUndefined(a.changed)||_.keys(a.changed).length===0?!0:!1},isAdd:function(a){return _.isUndefined(a.old)||_.keys(a.old).length===0?!0:!1},isUpdate:function(a){return!this.isRemove(a)&&!this.isAdd(a)?!0:!1}}),b.Events={},b.Events.bind=function(a,b,c){var d=this._callbacks||(this._callbacks={}),e=d[a]||(d[a]={}),f=e.tail||(e.tail=e.next={});return f.callback=b,f.context=c,e.tail=f.next={},this},b.Events.unbind=function(a,b){var c,d,e;if(!a)this._callbacks=null;else if(c=this._callbacks)if(!b)c[a]={};else if(d=c[a])while((e=d)&&(d=d.next)){if(d.callback!==b)continue;e.next=d.next,d.context=d.callback=null;break}return this},b.Events.trigger=function(a){var b,c,d,e,f,g=["all",a];if(!(c=this._callbacks))return this;while(f=g.pop()){if(!(b=c[f]))continue;e=f==="all"?arguments:Array.prototype.slice.call(arguments,1);while(b=b.next)(d=b.callback)&&d.apply(b.context||this,e)}return this},b.Events._buildEvent=function(a){return new b.Event(a)}}(this,_),function(a,_){var b=a.Miso||{};b.Builder={detectColumnType:function(a,c){var d=_.inject(c.slice(0,5),function(a,c){var d=b.typeOf(c);return c!==""&&a.indexOf(d)===-1&&!_.isNull(c)&&a.push(d),a},[]);return d.length===1?a.type=d[0]:a.type="mixed",a},detectColumnTypes:function(a,c){_.each(c,function(c,d){var e=a.column(d);if(e.type){e.force=!0;return}b.Builder.detectColumnType(e,c)},this)},cacheRows:function(a){b.Builder.clearRowCache(a),_.each(a._columns[a._columnPositionByName._id].data,function(b,c){a._rowPositionById[b]=c,a._rowIdByPosition.push(b)},a);var c=_.uniq(_.map(a._columns,function(a){return a.data.length}));if(c.length>1)throw new Error("Row lengths need to be the same. Empty values should be set to null."+_.map(a._columns,function(a){return a.data+"|||"}));a.length=c[0]},clearRowCache:function(a){a._rowPositionById={},a._rowIdByPosition=[]},cacheColumns:function(a){a._columnPositionByName={},_.each(a._columns,function(b,c){a._columnPositionByName[b.name]=c})}},Array.prototype.indexOf||(Array.prototype.indexOf=function(a,b){for(var c=b||0,d=this.length;c0&&(a=this.numericAt(c));return b.types[this.type].coerce(a,this)},_min:function(){var a=Infinity;for(var c=0;c=0;c--)a.apply(b||this,[this.rowByPosition(c),c])},eachColumn:function(a,b){var c=this.columnNames();for(var d=0;d=0)f(b,a-1),b--};if(c>2){g(c),d=c-1;while(d>1)e(d,0),d--,f(0,d)}else this.comparator(this.rowByPosition(0),this.rowByPosition(1))>0&&e(0,1);return this.comparator(this.rowByPosition(this.length-2),this.rowByPosition(this.length-1))>0&&e(this.length-1,this.length-2),this.syncable&&b.silent&&this.trigger("sort"),this},toJSON:function(){var a=[];for(var b=0;b0&&this.add(h)},blind:function(a){var b,c,d=[],e,f=_.keys(a),g=_.max(_.map(f,function(b){return a[b].length},this));for(var h=0;h0&&this.each(function(a,b){e.compute(a,b)},this),e},addColumn:function(a){return _.isUndefined(this.column(a.name))?(a=new b.Column(a),this._columns.push(a),this._columnPositionByName[a.name]=this._columns.length-1,a):!1},_addIdColumn:function(a){if(!_.isUndefined(this.column("_id")))return;var b=[];a&&a>0&&_.times(a,function(){b.push(_.uniqueId())}),this.addColumn({name:"_id",type:"number",data:b});if(this._columnPositionByName._id!==0){var c=this._columns[this._columnPositionByName._id],d=this._columnPositionByName._id;this._columns.splice(d,1),this._columns.unshift(c),this._columnPositionByName._id=0,_.each(this._columnPositionByName,function(a,b){b!=="_id"&&this._columnPositionByName[b]1&&(f=b),a},{}),new Error('You have more than one column named "'+f+'"')}_.each(a.table.rows,function(a){a=a.c;for(e=0;e0){var n=0,o=0,p=d.length;while(n11?"pm":"am";case"A":return o?o(h,i,!0):h>11?"PM":"AM";case"H":return h;case"HH":return C(h,2);case"h":return h%12||12;case"hh":return C(h%12||12,2);case"m":return i;case"mm":return C(i,2);case"s":return j;case"ss":return C(j,2);case"S":return~~(l/100);case"SS":return C(~~(l/10),2);case"SSS":return C(l,3);case"Z":return(m<0?"-":"+")+C(~~(Math.abs(m)/60),2)+":"+C(~~(Math.abs(m)%60),2);case"ZZ":return(m<0?"-":"+")+C(~~(10*Math.abs(m)/6),4);case"L":case"LL":case"LLL":case"LLLL":case"LT":return G(b,moment.longDateFormat[c]);default:return c.replace(/(^\[)|(\\)|\]$/g,"")}}var d=b.month(),e=b.date(),f=b.year(),g=b.day(),h=b.hours(),i=b.minutes(),j=b.seconds(),l=b.milliseconds(),m=-b.zone(),n=moment.ordinal,o=moment.meridiem;return c.replace(k,p)}function H(a){switch(a){case"DDDD":return o;case"YYYY":return p;case"S":case"SS":case"SSS":case"DDD":return n;case"MMM":case"MMMM":case"ddd":case"dddd":case"a":case"A":return q;case"Z":case"ZZ":return r;case"T":return s;case"MM":case"DD":case"dd":case"YY":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":return m;default:return new RegExp(a.replace("\\",""))}}function I(a,b,c,d){var e;switch(a){case"M":case"MM":c[1]=b==null?0:~~b-1;break;case"MMM":case"MMMM":for(e=0;e<12;e++)if(moment.monthsParse[e].test(b)){c[1]=e;break}break;case"D":case"DD":case"DDD":case"DDDD":c[2]=~~b;break;case"YY":b=~~b,c[0]=b+(b>70?1900:2e3);break;case"YYYY":c[0]=~~Math.abs(b);break;case"a":case"A":d.isPm=(b+"").toLowerCase()==="pm";break;case"H":case"HH":case"h":case"hh":c[3]=~~b;break;case"m":case"mm":c[4]=~~b;break;case"s":case"ss":c[5]=~~b;break;case"S":case"SS":case"SSS":c[6]=~~(("0."+b)*1e3);break;case"Z":case"ZZ":d.isUTC=!0,e=(b+"").match(w),e&&e[1]&&(d.tzh=~~e[1]),e&&e[2]&&(d.tzm=~~e[2]),e&&e[0]==="+"&&(d.tzh=-d.tzh,d.tzm=-d.tzm)}}function J(b,c){var d=[0,0,1,0,0,0,0],e={tzh:0,tzm:0},f=c.match(k),g,h;for(g=0;g0,N.apply({},i)}function P(a,b){moment.fn[a]=function(a){var c=this._isUTC?"UTC":"";return a!=null?(this._d["set"+c+b](a),this):this._d["get"+c+b]()}}function Q(a){moment.duration.fn[a]=function(){return this._data[a]}}function R(a,b){moment.duration.fn["as"+a]=function(){return+this/b}}var moment,c="1.6.2",d=Math.round,e,f={},g="en",h=typeof module!="undefined",i="months|monthsShort|monthsParse|weekdays|weekdaysShort|longDateFormat|calendar|relativeTime|ordinal|meridiem".split("|"),j=/^\/?Date\((\-?\d+)/i,k=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|dddd?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?|LT|LL?L?L?)/g,l=/([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi,m=/\d\d?/,n=/\d{1,3}/,o=/\d{3}/,p=/\d{4}/,q=/[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i,r=/Z|[\+\-]\d\d:?\d\d/i,s=/T/i,t=/^\s*\d{4}-\d\d-\d\d(T(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,u="YYYY-MM-DDTHH:mm:ssZ",v=[["HH:mm:ss.S",/T\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/T\d\d:\d\d:\d\d/],["HH:mm",/T\d\d:\d\d/],["HH",/T\d\d/]],w=/([\+\-]|\d\d)/gi,x="Month|Date|Hours|Minutes|Seconds|Milliseconds".split("|"),y={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6};moment=function(c,d){if(c===null||c==="")return null;var e,f,g;return moment.isMoment(c)?(e=new a(+c._d),g=c._isUTC):d?E(d)?e=L(c,d):e=J(c,d):(f=j.exec(c),e=c===b?new a:f?new a(+f[1]):c instanceof a?c:E(c)?F(c):typeof c=="string"?M(c):new a(c)),new z(e,g)},moment.utc=function(b,c){return E(b)?new z(new a(a.UTC.apply({},b)),!0):c&&b?moment(b+" +0000",c+" Z").utc():moment(b&&!r.exec(b)?b+"+0000":b).utc()},moment.unix=function(a){return moment(a*1e3)},moment.duration=function(a,b){var c=moment.isDuration(a),d=typeof a=="number",e=c?a._data:d?{}:a;return d&&(b?e[b]=a:e.milliseconds=a),new B(e)},moment.humanizeDuration=function(a,b,c){return moment.duration(a,b===!0?null:b).humanize(b===!0?!0:c)},moment.version=c,moment.defaultFormat=u,moment.lang=function(a,b){var c,d,e=[];if(!a)return g;if(b){for(c=0;c<12;c++)e[c]=new RegExp("^"+b.months[c]+"|^"+b.monthsShort[c].replace(".",""),"i");b.monthsParse=b.monthsParse||e,f[a]=b}if(f[a]){for(c=0;cc?1:0}function Z(a,b){return n[b]}function $(a){return"\\"+s[a]}function _(a){return q[a]}function ba(a,b){return function(c,d,e){return a.call(b,c,d,e)}}function bb(){}function bd(a,b){var c=n.length;return n[c]="'+\n_.escape("+b+") +\n'",m+c}function be(a,b){var c=n.length;return n[c]="'+\n((__t = ("+b+")) == null ? '' : __t) +\n'",m+c}function bf(a,b){var c=n.length;return n[c]="';\n"+b+";\n__p += '",m+c}function br(a,b,c,d){if(!a)return c;var e=a.length,f=arguments.length<3;d&&(b=ba(b,d));if(e===e>>>0){e&&f&&(c=a[--e]);while(e--)c=b(c,a[e],e,a);return c}var g,h=cw(a);e=h.length,e&&f&&(c=a[h[--e]]);while(e--)g=h[e],c=b(c,a[g],g,a);return c}function bu(a,b,c){if(typeof b=="string"){var d=b;b=function(a){return a[d]}}else c&&(b=ba(b,c));var e=bg(a,b).sort(Y),f=e.length;while(f--)e[f]=e[f].value;return e}function bv(a){if(!a)return[];if(a.toArray&&G.call(a.toArray)==w)return a.toArray();var b=a.length;return b===b>>>0?F.call(a):cz(a)}function bw(a){var b=[];if(!a)return b;var c=-1,d=a.length;while(++c-1})&&b.push(c);return b}function bD(a,b,c){if(a){var d=a.length;return b==null||c?a[d-1]:F.call(a,-b||d)}}function bE(a,b,c){if(!a)return-1;var d=a.length;c&&typeof c=="number"&&(d=(c<0?Math.max(0,d+c):Math.min(c,d-1))+1);while(d--)if(a[d]===b)return d;return-1}function bF(a,b,c){var d=-Infinity,e=d;if(!a)return e;var f,g=-1,h=a.length;if(!b){while(++ge&&(e=a[g]);return e}c&&(b=ba(b,c));while(++gd&&(d=f,e=a[g]);return e}function bG(a,b,c){var d=Infinity,e=d;if(!a)return e;var f,g=-1,h=a.length;if(!b){while(++g>>1,c(a[e])>>1,a[e]>>0?a.length:cw(a).length}function cA(a){return a==null?"":(a+"").replace(i,_)}function cB(a){return a}function cC(a){bl(ce(a),function(b){var c=N[b]=a[b];O.prototype[b]=function(){var a=[this._wrapped];arguments.length&&E.apply(a,arguments);var b=c.apply(N,a);return this._chain&&(b=new O(b),b._chain=!0),b}})}function cD(){return a._=f,this}function cE(a,b){if(!a)return null;var c=a[b];return G.call(c)==w?a[b]():c}function cF(a,b,c){c||(c={});var d,e,f,g=N.templateSettings,i=c.escape,k=c.evaluate,m=c.interpolate,p=c.variable;return i==null&&(i=g.escape),k==null&&(k=g.evaluate),m==null&&(m=g.interpolate),i&&(a=a.replace(i,bd)),m&&(e=a!=(a=a.replace(m,be))),k&&(d=a!=(a=a.replace(k,bf))),a="__p='"+a.replace(j,$).replace(h,Z)+"';\n",n.length=0,p||(p=g.variable,a="with ("+p+" || {}) {\n"+a+"\n}\n"),a="function("+p+") {\n"+"var __p"+(e?", __t":"")+(d?", __j = Array.prototype.join;\nfunction print() { __p += __j.call(arguments, '') }\n":";\n")+a+"return __p\n}",o&&(a+="\n//@ sourceURL=/lodash/template/source["+l++ +"]"),f=Function("_","return "+a)(N),b?f(b):(f.source=a,f)}function cG(a,b,c){var d=-1;if(c)while(++d/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:"obj"};var P=cF("var index, result<% if (init) { %> = <%= init %><% } %>;\n<%= exit %>;\n<%= top %>;\n<% if (arrayBranch) { %>var length = <%= firstArg %>.length; index = -1; <% if (objectBranch) { %>\nif (length === length >>> 0) {<% } %>\n <%= arrayBranch.beforeLoop %>;\n while (<%= arrayBranch.loopExp %>) {\n <%= arrayBranch.inLoop %>;\n } <% if (objectBranch) { %>\n}\n<% }}if (objectBranch) { if (arrayBranch) { %>else {\n<% } if (!hasDontEnumBug) { %> var skipProto = typeof <%= iteratedObject %> == 'function';\n<% } %> <%= objectBranch.beforeLoop %>;\n for (<%= objectBranch.loopExp %>) { \n<% if (hasDontEnumBug) { if (useHas) { %> if (<%= hasExp %>) {\n <% } %> <%= objectBranch.inLoop %>;<% if (useHas) { %>\n }<% } } else { %> if (!(skipProto && index == 'prototype')<% if (useHas) { %> && <%= hasExp %><% } %>) {\n <%= objectBranch.inLoop %>;\n } <% } %>\n } <% if (hasDontEnumBug) { %>\n var ctor = <%= iteratedObject %>.constructor;\n <% for (var k = 0; k < 7; k++) { %>\n index = '<%= shadowed[k] %>';\n if (<% if (shadowed[k] == 'constructor') { %>!(ctor && ctor.prototype === <%= iteratedObject %>) && <% } %><%= hasExp %>) {\n <%= objectBranch.inLoop %>;\n }<% } } if (arrayBranch) { %>\n}<% }} %>\n<%= bottom %>;\nreturn result"),Q={args:"collection, callback, thisArg",init:"collection",top:"if (!callback) {\n callback = identity\n}\nelse if (thisArg) {\n callback = iteratorBind(callback, thisArg)\n}",inLoop:"callback(collection[index], index, collection)"},R={init:"true",inLoop:"if (!callback(collection[index], index, collection)) return !result"},S={args:"object",init:"object",top:"for (var source, sourceIndex = 1, length = arguments.length; sourceIndex < length; sourceIndex++) {\n source = arguments[sourceIndex];\n"+(d?" if (source) {":""),loopExp:"index in source",useHas:!1,inLoop:"object[index] = source[index]",bottom:(d?" }\n":"")+"}"},T={init:"[]",inLoop:"callback(collection[index], index, collection) && result.push(collection[index])"},U={top:"if (thisArg) callback = iteratorBind(callback, thisArg)"},V={inLoop:{object:Q.inLoop}},W={init:"",exit:"if (!collection) return []",beforeLoop:{array:"result = Array(length)",object:"result = []"},inLoop:{array:"result[index] = callback(collection[index], index, collection)",object:"result.push(callback(collection[index], index, collection))"}},bc=X({args:"object",exit:"if (!objectTypes[typeof object] || object === null) throw TypeError()",init:"[]",inLoop:"result.push(index)"}),bg=X(W,{args:"collection, callback",inLoop:{array:"result[index] = {\n criteria: callback(collection[index], index, collection),\n value: collection[index]\n}",object:"result.push({\n criteria: callback(collection[index], index, collection),\n value: collection[index]\n})"}}),bh=X({args:"collection, target",init:"false",inLoop:"if (collection[index] === target) return true"}),bi=X(Q,R),bj=X(Q,T),bk=X(Q,U,{init:"",inLoop:"if (callback(collection[index], index, collection)) return collection[index]"}),bl=X(Q,U),bm=X(Q,{init:"{}",top:"var prop, isFunc = typeof callback == 'function';\nif (isFunc && thisArg) callback = iteratorBind(callback, thisArg)",inLoop:"prop = isFunc\n ? callback(collection[index], index, collection)\n : collection[index][callback];\n(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(collection[index])"}),bn=X(W,{args:"collection, methodName",top:"var args = slice.call(arguments, 2),\n isFunc = typeof methodName == 'function'",inLoop:{array:"result[index] = (isFunc ? methodName : collection[index][methodName]).apply(collection[index], args)",object:"result.push((isFunc ? methodName : collection[index][methodName]).apply(collection[index], args))"}}),bo=X(Q,W),bp=X(W,{args:"collection, property",inLoop:{array:"result[index] = collection[index][property]",object:"result.push(collection[index][property])"}}),bq=X({args:"collection, callback, accumulator, thisArg",init:"accumulator",top:"var noaccum = arguments.length < 3;\nif (thisArg) callback = iteratorBind(callback, thisArg)",beforeLoop:{array:"if (noaccum) result = collection[++index]"},inLoop:{array:"result = callback(result, collection[index], index, collection)",object:"result = noaccum\n ? (noaccum = false, collection[index])\n : callback(result, collection[index], index, collection)"}}),bs=X(Q,T,{inLoop:"!"+T.inLoop}),bt=X(Q,R,{init:"false",inLoop:R.inLoop.replace("!","")}),ca=X(S,{inLoop:"if (object[index] == null)"+S.inLoop}),cb=X(S),cc=X(Q,U,V,{useHas:!1}),cd=X(Q,U,V),ce=X({args:"object",init:"[]",useHas:!1,inLoop:"if (toString.call(object[index]) == funcClass) result.push(index)",bottom:"result.sort()"}),cg=function(a){return G.call(a)=="[object Arguments]"};cg(arguments)||(cg=function(a){return!!a&&!!D.call(a,"callee")});var ch=I||function(a){return G.call(a)==t},cl=X({args:"value",init:"true",top:"var className = toString.call(value);\nif (className == arrayClass || className == stringClass) return !value.length",inLoop:{object:"return false"}}),cw=K?function(a){return typeof a=="function"?bc(a):K(a)}:bc,cz=X({args:"object",init:"[]",inLoop:"result.push(object[index])"});N.VERSION="0.3.2",N.after=bP,N.bind=bQ,N.bindAll=bR,N.chain=cI,N.clone=b_,N.compact=bw,N.compose=bS,N.contains=bh,N.debounce=bT,N.defaults=ca,N.defer=bV,N.delay=bU,N.difference=bx,N.escape=cA,N.every=bi,N.extend=cb,N.filter=bj,N.find=bk,N.first=by,N.flatten=bz,N.forEach=bl,N.forIn=cc,N.forOwn=cd,N.functions=ce,N.groupBy=bm,N.has=cf,N.identity=cB,N.indexOf=bA,N.initial=bB,N.intersection=bC,N.invoke=bn,N.isArguments=cg,N.isArray=ch,N.isBoolean=ci,N.isDate=cj,N.isElement=ck,N.isEmpty=cl,N.isEqual=cm,N.isFinite=cn,N.isFunction=co,N.isNaN=cq,N.isNull=cr,N.isNumber=cs,N.isObject=cp,N.isRegExp=ct,N.isString=cu,N.isUndefined=cv,N.keys=cw,N.last=bD,N.lastIndexOf=bE,N.map=bo,N.max=bF,N.memoize=bW,N.min=bG,N.mixin=cC,N.noConflict=cD,N.once=bX,N.partial=bY,N.pick=cx,N.pluck=bp,N.range=bH,N.reduce=bq,N.reduceRight=br,N.reject=bs,N.rest=bI,N.result=cE,N.shuffle=bJ,N.size=cy,N.some=bt,N.sortBy=bu,N.sortedIndex=bK,N.tap=cJ,N.template=cF,N.throttle=bZ,N.times=cG,N.toArray=bv,N.union=bL,N.uniq=bM,N.uniqueId=cH,N.values=cz,N.without=bN,N.wrap=b$,N.zip=bO,N.all=bi,N.any=bt,N.collect=bo,N.detect=bk,N.each=bl,N.foldl=bq,N.foldr=br,N.head=by,N.include=bh,N.inject=bq,N.methods=ce,N.select=bj,N.tail=bI,N.take=by,N.unique=bM,N._iteratorTemplate=P,N._shimKeys=bc,O.prototype=N.prototype,cC(N),O.prototype.chain=cK,O.prototype.value=cL,bl(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=A[a];O.prototype[a]=function(){var a=this._wrapped;return b.apply(a,arguments),a.length===0&&delete a[0],this._chain&&(a=new O(a),a._chain=!0),a}}),bl(["concat","join","slice"],function(a){var b=A[a];O.prototype[a]=function(){var a=this._wrapped,c=b.apply(a,arguments);return this._chain&&(c=new O(c),c._chain=!0),c}}),typeof define=="function"&&typeof define.amd=="object"&&define.amd?(a._=N,define(function(){return N})):c?typeof module=="object"&&module&&module.exports==c?(module.exports=N)._=N:c._=N:a._=N}(this),function(){var a=this.math={};a.mean=a.ave=a.average=function(b,c){return a.sum(b,c)/_(b).size()},a.median=function(b){var c=(b.length+1)/2,d=a.sort(b);return d.length%2?d[c-1]:(d[c-1.5]+d[c-.5])/2},a.pow=function(a,b){if(_.isNumber(a))return Math.pow(a,b);if(_.isArray(a))return _.map(a,function(a){return _.pow(a,b)})},a.scale=function(a,b){var b=b||1,c=_.max(a);return _.map(a,function(a){return a*(b/c)})},a.slope=function(a,b){return(b[1]-a[1])/(b[0]-a[0])},a.sort=function(a){return a.sort(function(a,b){return a-b})},a.stdDeviation=a.sigma=function(a){return Math.sqrt(_(a).variance())},a.sum=function(a,b){if(_.isArray(a)&&typeof a[0]=="number")var c=a;else var b=b||"value",c=_(a).pluck(b);var d=0;for(var e=0,f=c.length;e1?h.call(arguments,0):c,--f||i.resolveWith(i,b)}}function m(a){return function(b){e[a]=arguments.length>1?h.call(arguments,0):b,i.notifyWith(k,e)}}var b=h.call(arguments,0),c=0,d=b.length,e=new Array(d),f=d,g=d,i=d<=1&&a&&j(a.promise)?a:n.Deferred(),k=i.promise();if(d>1){for(;cb?1:0},numeric:function(a){return _.isNaN(Number(a))?null:Number(a)}},string:{name:"string",coerce:function(a){return a==null?null:a.toString()},test:function(a){return a===null||typeof a=="undefined"||typeof a=="string"},compare:function(a,b){return a==null&&b!=null?-1:a!=null&&b==null?1:ab?1:0},numeric:function(a){return _.isNaN(+a)||a===null?null:_.isNumber(+a)?+a:null}},"boolean":{name:"boolean",regexp:/^(true|false)$/,coerce:function(a){return a==="false"?!1:Boolean(a)},test:function(a){return a===null||typeof a=="undefined"||typeof a=="boolean"||this.regexp.test(a)?!0:!1},compare:function(a,b){return a==null&&b!=null?-1:a!=null&&b==null?1:a==null&&b==null?0:a===b?0:ab?1:0},numeric:function(a){return a.valueOf()}}}}(this,_),function(a,_){var b=a.Miso||(a.Miso={});b.Event=function(a){_.isArray(a)||(a=[a]),this.deltas=a},_.extend(b.Event.prototype,{affectedColumns:function(){var a=[];return _.each(this.deltas,function(b){b.old=b.old||[],b.changed=b.changed||[],a=_.chain(a).union(_.keys(b.old),_.keys(b.changed)).reject(function(a){return a==="_id"}).value()}),a}}),_.extend(b.Event,{isRemove:function(a){return _.isUndefined(a.changed)||_.keys(a.changed).length===0?!0:!1},isAdd:function(a){return _.isUndefined(a.old)||_.keys(a.old).length===0?!0:!1},isUpdate:function(a){return!this.isRemove(a)&&!this.isAdd(a)?!0:!1}}),b.Events={},b.Events.bind=function(a,b,c){var d=this._callbacks||(this._callbacks={}),e=d[a]||(d[a]={}),f=e.tail||(e.tail=e.next={});return f.callback=b,f.context=c,e.tail=f.next={},this},b.Events.unbind=function(a,b){var c,d,e;if(!a)this._callbacks=null;else if(c=this._callbacks)if(!b)c[a]={};else if(d=c[a])while((e=d)&&(d=d.next)){if(d.callback!==b)continue;e.next=d.next,d.context=d.callback=null;break}return this},b.Events.trigger=function(a){var b,c,d,e,f,g=["all",a];if(!(c=this._callbacks))return this;while(f=g.pop()){if(!(b=c[f]))continue;e=f==="all"?arguments:Array.prototype.slice.call(arguments,1);while(b=b.next)(d=b.callback)&&d.apply(b.context||this,e)}return this},b.Events._buildEvent=function(a){return new b.Event(a)}}(this,_),function(a,_){var b=a.Miso||{};b.Builder={detectColumnType:function(a,c){var d=_.inject(c.slice(0,5),function(a,c){var d=b.typeOf(c);return c!==""&&a.indexOf(d)===-1&&!_.isNull(c)&&a.push(d),a},[]);return d.length===1?a.type=d[0]:a.type="mixed",a},detectColumnTypes:function(a,c){_.each(c,function(c,d){var e=a.column(d);if(e.type){e.force=!0;return}b.Builder.detectColumnType(e,c)},this)},cacheRows:function(a){b.Builder.clearRowCache(a),_.each(a._columns[a._columnPositionByName._id].data,function(b,c){a._rowPositionById[b]=c,a._rowIdByPosition.push(b)},a);var c=_.uniq(_.map(a._columns,function(a){return a.data.length}));if(c.length>1)throw new Error("Row lengths need to be the same. Empty values should be set to null."+_.map(a._columns,function(a){return a.data+"|||"}));a.length=c[0]},clearRowCache:function(a){a._rowPositionById={},a._rowIdByPosition=[]},cacheColumns:function(a){a._columnPositionByName={},_.each(a._columns,function(b,c){a._columnPositionByName[b.name]=c})}},Array.prototype.indexOf||(Array.prototype.indexOf=function(a,b){for(var c=b||0,d=this.length;c0&&(a=this.numericAt(c));return b.types[this.type].coerce(a,this)},_min:function(){var a=Infinity;for(var c=0;c=0;c--)a.apply(b||this,[this.rowByPosition(c),c])},eachColumn:function(a,b){var c=this.columnNames();for(var d=0;d=0)f(b,a-1),b--};if(c>2){g(c),d=c-1;while(d>1)e(d,0),d--,f(0,d)}else this.comparator(this.rowByPosition(0),this.rowByPosition(1))>0&&e(0,1);return this.comparator(this.rowByPosition(this.length-2),this.rowByPosition(this.length-1))>0&&e(this.length-1,this.length-2),this.syncable&&b.silent&&this.trigger("sort"),this},toJSON:function(){var a=[];for(var b=0;b0&&_.times(a,function(){b.push(_.uniqueId())}),this.addColumn({name:"_id",type:"number",data:b});if(this._columnPositionByName._id!==0){var c=this._columns[this._columnPositionByName._id],d=this._columnPositionByName._id;this._columns.splice(d,1),this._columns.unshift(c),this._columnPositionByName._id=0,_.each(this._columnPositionByName,function(a,b){b!=="_id"&&this._columnPositionByName[b]1&&(f=b),a},{}),new Error('You have more than one column named "'+f+'"')}_.each(a.table.rows,function(a){a=a.c;for(e=0;e0){var n=0,o=0,p=d.length;while(n70?1900:2e3);break;case"YYYY":c[0]=~~Math.abs(b);break;case"a":case"A":d.isPm=(b+"").toLowerCase()==="pm";break;case"H":case"HH":case"h":case"hh":c[3]=~~b;break;case"m":case"mm":c[4]=~~b;break;case"s":case"ss":c[5]=~~b;break;case"S":case"SS":case"SSS":c[6]=~~(("0."+b)*1e3);break;case"Z":case"ZZ":d.isUTC=!0,e=(b+"").match(y),e&&e[1]&&(d.tzh=~~e[1]),e&&e[2]&&(d.tzm=~~e[2]),e&&e[0]==="+"&&(d.tzh=-d.tzh,d.tzm=-d.tzm)}}function W(a,b){var c=[0,0,1,0,0,0,0],d={tzh:0,tzm:0},e=b.match(k),f,g;for(f=0;f0,j[4]=c,Z.apply({},j)}function _(a,b){moment.fn[a]=function(a){var c=this._isUTC?"UTC":"";return a!=null?(this._d["set"+c+b](a),this):this._d["get"+c+b]()}}function ba(a){moment.duration.fn[a]=function(){return this._data[a]}}function bb(a,b){moment.duration.fn["as"+a]=function(){return+this/b}}var moment,c="1.7.0",d=Math.round,e,f={},g="en",h=typeof module!="undefined"&&module.exports,i="months|monthsShort|weekdays|weekdaysShort|weekdaysMin|longDateFormat|calendar|relativeTime|ordinal|meridiem".split("|"),j=/^\/?Date\((\-?\d+)/i,k=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?)/g,l=/(LT|LL?L?L?)/g,m=/(^\[)|(\\)|\]$/g,n=/([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi,o=/\d\d?/,p=/\d{1,3}/,q=/\d{3}/,r=/\d{1,4}/,s=/[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i,t=/Z|[\+\-]\d\d:?\d\d/i,u=/T/i,v=/^\s*\d{4}-\d\d-\d\d(T(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,w="YYYY-MM-DDTHH:mm:ssZ",x=[["HH:mm:ss.S",/T\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/T\d\d:\d\d:\d\d/],["HH:mm",/T\d\d:\d\d/],["HH",/T\d\d/]],y=/([\+\-]|\d\d)/gi,z="Month|Date|Hours|Minutes|Seconds|Milliseconds".split("|"),A={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},B={},C={M:"(a=t.month()+1)",MMM:'v("monthsShort",t.month())',MMMM:'v("months",t.month())',D:"(a=t.date())",DDD:"(a=new Date(t.year(),t.month(),t.date()),b=new Date(t.year(),0,1),a=~~(((a-b)/864e5)+1.5))",d:"(a=t.day())",dd:'v("weekdaysMin",t.day())',ddd:'v("weekdaysShort",t.day())',dddd:'v("weekdays",t.day())',w:"(a=new Date(t.year(),t.month(),t.date()-t.day()+5),b=new Date(a.getFullYear(),0,4),a=~~((a-b)/864e5/7+1.5))",YY:"p(t.year()%100,2)",YYYY:"p(t.year(),4)",a:"m(t.hours(),t.minutes(),!0)",A:"m(t.hours(),t.minutes(),!1)",H:"t.hours()",h:"t.hours()%12||12",m:"t.minutes()",s:"t.seconds()",S:"~~(t.milliseconds()/100)",SS:"p(~~(t.milliseconds()/10),2)",SSS:"p(t.milliseconds(),3)",Z:'((a=-t.zone())<0?((a=-a),"-"):"+")+p(~~(a/60),2)+":"+p(~~a%60,2)',ZZ:'((a=-t.zone())<0?((a=-a),"-"):"+")+p(~~(10*a/6),4)'},D="DDD w M D d".split(" "),E="M D H h m s w".split(" ");while(D.length)e=D.pop(),C[e+"o"]=C[e]+"+o(a)";while(E.length)e=E.pop(),C[e+e]="p("+C[e]+",2)";C.DDDD="p("+C.DDD+",3)",moment=function(c,d){if(c===null||c==="")return null;var e,f;return moment.isMoment(c)?new F(new a(+c._d),c._isUTC,c._lang):(d?K(d)?e=X(c,d):e=W(c,d):(f=j.exec(c),e=c===b?new a:f?new a(+f[1]):c instanceof a?c:K(c)?M(c):typeof c=="string"?Y(c):new a(c)),new F(e))},moment.utc=function(a,b){return K(a)?new F(M(a,!0),!0):(typeof a=="string"&&!t.exec(a)&&(a+=" +0000",b&&(b+=" Z")),moment(a,b).utc())},moment.unix=function(a){return moment(a*1e3)},moment.duration=function(a,b){var c=moment.isDuration(a),d=typeof a=="number",e=c?a._data:d?{}:a,f;return d&&(b?e[b]=a:e.milliseconds=a),f=new G(e),c&&(f._lang=a._lang),f},moment.humanizeDuration=function(a,b,c){return moment.duration(a,b===!0?null:b).humanize(b===!0?!0:c)},moment.version=c,moment.defaultFormat=w,moment.lang=function(a,b){var c;if(!a)return g;(b||!f[a])&&N(a,b);if(f[a]){for(c=0;c11?c?"pm":"PM":c?"am":"AM"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinal:function(a){var b=a%10;return~~(a%100/10)===1?"th":b===1?"st":b===2?"nd":b===3?"rd":"th"}}),moment.fn=F.prototype={clone:function(){return moment(this)},valueOf:function(){return+this._d},unix:function(){return Math.floor(+this._d/1e3)},toString:function(){return this._d.toString()},toDate:function(){return this._d},toArray:function(){var a=this;return[a.year(),a.month(),a.date(),a.hours(),a.minutes(),a.seconds(),a.milliseconds(),!!this._isUTC]},isValid:function(){return this._a?!L(this._a,(this._a[7]?moment.utc(this):this).toArray()):!isNaN(this._d.getTime())},utc:function(){return this._isUTC=!0,this},local:function(){return this._isUTC=!1,this},format:function(a){return T(this,a?a:moment.defaultFormat)},add:function(a,b){var c=b?moment.duration(+b,a):moment.duration(a);return J(this,c,1),this},subtract:function(a,b){var c=b?moment.duration(+b,a):moment.duration(a);return J(this,c,-1),this},diff:function(a,b,c){var e=this._isUTC?moment(a).utc():moment(a).local(),f=(this.zone()-e.zone())*6e4,g=this._d-e._d-f,h=this.year()-e.year(),i=this.month()-e.month(),j=this.date()-e.date(),k;return b==="months"?k=h*12+i+j/30:b==="years"?k=h+(i+j/30)/12:k=b==="seconds"?g/1e3:b==="minutes"?g/6e4:b==="hours"?g/36e5:b==="days"?g/864e5:b==="weeks"?g/6048e5:g,c?k:d(k)},from:function(a,b){return moment.duration(this.diff(a)).lang(this._lang).humanize(!b)},fromNow:function(a){return this.from(moment(),a)},calendar:function(){var a=this.diff(moment().sod(),"days",!0),b=this.lang().calendar,c=b.sameElse,d=a<-6?c:a<-1?b.lastWeek:a<0?b.lastDay:a<1?b.sameDay:a<2?b.nextDay:a<7?b.nextWeek:c;return this.format(typeof d=="function"?d.apply(this):d)},isLeapYear:function(){var a=this.year();return a%4===0&&a%100!==0||a%400===0},isDST:function(){return this.zone()=(c||n),f=e?{}:a;if(e){var g,h=b-1;while(++h-1}return cM(f,a,b)>-1}}function bD(){var a,b,c,d=-1,e=arguments.length,f={bottom:"",exit:"",init:"",top:"",arrayBranch:{beforeLoop:""},objectBranch:{beforeLoop:""}};while(++dc?1:d";var c=D.length;return D[c]="' +\n__e("+b+") +\n'",C+c}function bL(a,b,c,d){if(d){var e=D.length;return D[e]="';\n"+d+";\n__p += '",C+e}return b?bK(null,b):bM(null,c)}function bM(a,b){if(a&&p.test(b))return"";var c=D.length;return D[c]="' +\n((__t = ("+b+")) == null ? '' : __t) +\n'",C+c}function bN(a){return bn[a]}function bO(a){return J.call(a)==O}function bQ(a){return typeof a=="function"}function bR(a,b){return a?a==j||a.__proto__==j&&(b||!bO(a)):!1}function bT(a,b,c,d,e){if(a==null)return a;c&&(b=!1),e||(e={value:null}),e.value==null&&(e.value=!!(i.clone||k.clone||l.clone));var f=bo[typeof a];if((f||e.value)&&a.clone&&bQ(a.clone))return e.value=null,a.clone(b);if(f){var g=J.call(a);if(!bl[g]||bb&&bO(a))return a;var h=g==P;f=h||(g==U?bR(a,!0):f)}if(!f||!b)return f?h?I.call(a):bW({},a):a;var j=a.constructor;switch(g){case Q:return new j(a==!0);case R:return new j(+a);case T:case W:return new j(a);case V:return j(a.source,u.exec(a))}d||(d=[]);var m=d.length;while(m--)if(d[m].source==a)return d[m].value;m=a.length;var n=h?j(m):{};d.push({value:n,source:a});if(h){var o=-1;while(++o-1&&c===c>>>0&&bQ(a.splice)?c:cm(a).length}function cC(a,b,c,d){if(!a)return c;var e=a.length,f=arguments.length<3;d&&(b=bI(b,d));if(e>-1&&e===e>>>0){var g=bd&&J.call(a)==W?a.split(""):a;e&&f&&(c=g[--e]);while(e--)c=b(c,g[e],e,a);return c}var h,i=cm(a);e=i.length,e&&f&&(c=a[i[--e]]);while(e--)h=i[e],c=b(c,a[h],h,a);return c}function cG(a){if(!a)return[];if(a.toArray&&bQ(a.toArray))return a.toArray();var b=a.length;return b>-1&&b===b>>>0?(bc?J.call(a)==W:typeof a=="string")?a.split(""):I.call(a):cq(a)}function cI(a){var b=[];if(!a)return b;var c=-1,d=a.length;while(++ce&&(e=a[g]);return e}c&&(b=bI(b,c));while(++gd&&(d=f,e=a[g]);return e}function cS(a,b,c){var d=Infinity,e=d;if(!a)return e;var f,g=-1,h=a.length;if(!b){while(++g>>1,c(a[e])>>1,a[e]2)return K.call.apply(K,arguments);var e=I.call(arguments,2);return f}function dd(){var a=arguments;return function(){var b=arguments,c=a.length;while(c--)b=[a[c].apply(this,b)];return b[0]}}function de(a,b,c){function h(){g=null,c||a.apply(f,d)}var d,e,f,g;return function(){var i=c&&!g;return d=arguments,f=this,X(g),g=Y(h,b),i&&(e=a.apply(f,d)),e}}function df(a,c){var d=I.call(arguments,2);return Y(function(){return a.apply(b,d)},c)}function dg(a){var c=I.call(arguments,1);return Y(function(){return a.apply(b,c)},1)}function dh(a,b){var c={};return function(){var d=b?b.apply(this,arguments):arguments[0];return F.call(c,d)?c[d]:c[d]=a.apply(this,arguments)}}function di(a){var b,c=!1;return function(){return c?b:(c=!0,b=a.apply(this,arguments),a=null,b)}}function dj(a){var b=I.call(arguments,1),c=b.length;return function(){var d,e=arguments;return e.length&&(b.length=c,G.apply(b,e)),d=b.length==1?a.call(this,b[0]):a.apply(this,b),b.length=c,d}}function dk(a,b){function h(){g=new Date,f=null,a.apply(e,c)}var c,d,e,f,g=0;return function(){var i=new Date,j=b-(i-g);return c=arguments,e=this,j<=0?(g=i,d=a.apply(e,c)):f||(f=Y(h,j)),d}}function dl(a,b){return function(){var c=[a];return arguments.length&&G.apply(c,arguments),b.apply(this,c)}}function dm(a){return a==null?"":(a+"").replace(y,bH)}function dn(a){return a}function dp(a){cw(bZ(a),function(b){var c=bq[b]=a[b];br.prototype[b]=function(){var a=[this._wrapped];arguments.length&&G.apply(a,arguments);var b=c.apply(bq,a);return this._chain&&(b=new br(b),b._chain=!0),b}})}function dq(){return a._=o,this}function dr(a,b){if(!a)return null;var c=a[b];return bQ(c)?a[b]():c}function ds(a,b,g){g||(g={}),a+="";var h,i,j=g.escape,k=g.evaluate,l=g.interpolate,m=bq.templateSettings,n=g.variable||m.variable,o=n;j==null&&(j=m.escape),k==null&&(k=m.evaluate||!1),l==null&&(l=m.interpolate),j&&(a=a.replace(j,bK)),l&&(a=a.replace(l,bM)),k!=c&&(c=k,f=RegExp("|"+(k?"|"+k.source:""),"g")),h=D.length,a=a.replace(f,bL),h=h!=D.length,a="__p += '"+a.replace(z,bG).replace(x,bF)+"';\n",D.length=0,o||(n=d||"obj",h?a="with ("+n+") {\n"+a+"\n}\n":(n!=d&&(d=n,e=RegExp("(\\(\\s*)"+n+"\\."+n+"\\b","g")),a=a.replace(v,"$&"+n+".").replace(e,"$1__d"))),a=(h?a.replace(r,""):a).replace(s,"$1").replace(t,"$1;"),a="function("+n+") {\n"+(o?"":n+" || ("+n+" = {});\n")+"var __t, __p = '', __e = _.escape"+(h?", __j = Array.prototype.join;\nfunction print() { __p += __j.call(arguments, '') }\n":(o?"":", __d = "+n+"."+n+" || "+n)+";\n")+a+"return __p\n}",bj&&(a+="\n//@ sourceURL=/lodash/template/source["+B++ +"]");try{i=Function("_","return "+a)(bq)}catch(p){i=function(){throw p}}return b?i(b):(i.source=a,i)}function dt(a,b,c){var d=-1;if(c)while(++d|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/,q=/&(?:amp|lt|gt|quot|#x27);/g,r=/\b__p \+= '';/g,s=/\b(__p \+=) '' \+/g,t=/(__e\(.*?\)|\b__t\)) \+\n'';/g,u=/\w*$/,v=/(?:__e|__t = )\(\s*(?![\d\s"']|this\.)/g,w=RegExp("^"+(j.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),x=/__token__(\d+)/g,y=/[&<>"']/g,z=/['\n\r\t\u2028\u2029\\]/g,A=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],B=0,C="__token__",D=[],E=h.concat,F=j.hasOwnProperty,G=h.push,H=j.propertyIsEnumerable,I=h.slice,J=j.toString,K=w.test(K=I.bind)&&K,L=w.test(L=Array.isArray)&&L,M=a.isFinite,N=w.test(N=Object.keys)&&N,O="[object Arguments]",P="[object Array]",Q="[object Boolean]",R="[object Date]",S="[object Function]",T="[object Number]",U="[object Object]",V="[object RegExp]",W="[object String]",X=a.clearTimeout,Y=a.setTimeout,Z,$,_,ba=!0;(function(){function c(){this.x=1}var a={0:1,length:1},b=[];c.prototype={valueOf:1,y:1};for(var d in new c)b.push(d);for(d in arguments)ba=!d;Z=(b+"").length<4,_=b[0]!="x",$=(b.splice.call(a,0,1),a[0])})(1);var bb=!bO(arguments),bc=I.call("x")[0]!="x",bd="x"[0]+Object("x")[0]!="xx";try{var be=({toString:0}+"",J.call(a.document||0)==U)}catch(bf){}var bg=K&&/\n|Opera/.test(K+J.call(a.opera)),bh=N&&/^.+$|true/.test(N+!!a.attachEvent),bi=!bg;try{var bj=(Function("//@")(),!a.attachEvent)}catch(bf){}var bk={};bk[Q]=bk[R]=bk[S]=bk[T]=bk[U]=bk[V]=!1,bk[O]=bk[P]=bk[W]=!0;var bl={};bl[O]=bl[S]=!1,bl[P]=bl[Q]=bl[R]=bl[T]=bl[U]=bl[V]=bl[W]=!0;var bm={"&":"&","<":"<",">":">",'"':""","'":"'"},bn={"&":"&","<":"<",">":">",""":'"',"'":"'"},bo={"boolean":!1,"function":!0,object:!0,number:!1,string:!1,"undefined":!1,unknown:!0},bp={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"};bq.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:""};var bs=ds("<% if (useStrict) { %>'use strict';\n<% } %>var index, value, iteratee = <%= firstArg %>, result<% if (init) { %> = <%= init %><% } %>;\n<%= exit %>;\n<%= top %>;\n<% if (arrayBranch) { %>var length = iteratee.length; index = -1; <% if (objectBranch) { %>\nif (length > -1 && length === length >>> 0) {<% } %> <% if (noCharByIndex) { %>\n if (toString.call(iteratee) == stringClass) {\n iteratee = iteratee.split('')\n } <% } %>\n <%= arrayBranch.beforeLoop %>;\n while (++index < length) {\n value = iteratee[index];\n <%= arrayBranch.inLoop %>\n } <% if (objectBranch) { %>\n}<% } %><% } %><% if (objectBranch) { %> <% if (arrayBranch) { %>\nelse { <% } else if (noArgsEnum) { %>\n var length = iteratee.length; index = -1;\n if (length && isArguments(iteratee)) {\n while (++index < length) {\n value = iteratee[index += ''];\n <%= objectBranch.inLoop %>\n }\n } else { <% } %> <% if (!hasDontEnumBug) { %>\n var skipProto = typeof iteratee == 'function' && \n propertyIsEnumerable.call(iteratee, 'prototype');\n <% } %> <% if (isKeysFast && useHas) { %>\n var ownIndex = -1,\n ownProps = objectTypes[typeof iteratee] ? nativeKeys(iteratee) : [],\n length = ownProps.length;\n\n <%= objectBranch.beforeLoop %>;\n while (++ownIndex < length) {\n index = ownProps[ownIndex];\n <% if (!hasDontEnumBug) { %>if (!(skipProto && index == 'prototype')) {\n <% } %> value = iteratee[index];\n <%= objectBranch.inLoop %>\n <% if (!hasDontEnumBug) { %>}\n<% } %> } <% } else { %>\n <%= objectBranch.beforeLoop %>;\n for (index in iteratee) { <% if (!hasDontEnumBug || useHas) { %>\n if (<% if (!hasDontEnumBug) { %>!(skipProto && index == 'prototype')<% } if (!hasDontEnumBug && useHas) { %> && <% } if (useHas) { %>hasOwnProperty.call(iteratee, index)<% } %>) { <% } %>\n value = iteratee[index];\n <%= objectBranch.inLoop %>;\n <% if (!hasDontEnumBug || useHas) { %>}\n<% } %> } <% } %> <% if (hasDontEnumBug) { %>\n\n var ctor = iteratee.constructor;\n <% for (var k = 0; k < 7; k++) { %>\n index = '<%= shadowed[k] %>';\n if (<% if (shadowed[k] == 'constructor') { %>!(ctor && ctor.prototype === iteratee) && <% } %>hasOwnProperty.call(iteratee, index)) {\n value = iteratee[index];\n <%= objectBranch.inLoop %>\n } <% } %> <% } %> <% if (arrayBranch || noArgsEnum) { %>\n}<% } %><% } %>\n<%= bottom %>;\nreturn result"),bt={args:"collection, callback, thisArg",init:"collection",top:"if (!callback) {\n callback = identity\n}\nelse if (thisArg) {\n callback = iteratorBind(callback, thisArg)\n}",inLoop:"if (callback(value, index, collection) === false) return result"},bu={init:"{}",top:"var prop;\nif (typeof callback != 'function') {\n var valueProp = callback;\n callback = function(value) { return value[valueProp] }\n}\nelse if (thisArg) {\n callback = iteratorBind(callback, thisArg)\n}",inLoop:"prop = callback(value, index, collection);\n(hasOwnProperty.call(result, prop) ? result[prop]++ : result[prop] = 1)"},bv={useHas:!1,args:"object, callback, thisArg",init:"{}",top:"var isFunc = typeof callback == 'function';\nif (!isFunc) {\n var props = concat.apply(ArrayProto, arguments)\n} else if (thisArg) {\n callback = iteratorBind(callback, thisArg)\n}",inLoop:"if (isFunc\n ? !callback(value, index, object)\n : indexOf(props, index) < 0\n) result[index] = value"},bw={init:"true",inLoop:"if (!callback(value, index, collection)) return !result"},bx={useHas:!1,useStrict:!1,args:"object",init:"object",top:"for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {\n if (iteratee = arguments[argsIndex]) {",inLoop:"result[index] = value",bottom:" }\n}"},by={init:"[]",inLoop:"callback(value, index, collection) && result.push(value)"},bz={top:"if (thisArg) callback = iteratorBind(callback, thisArg)"},bA={inLoop:{object:bt.inLoop}},bB={init:"",exit:"if (!collection) return []",beforeLoop:{array:"result = Array(length)",object:"result = "+(bh?"Array(length)":"[]")},inLoop:{array:"result[index] = callback(value, index, collection)",object:"result"+(bh?"[ownIndex] = ":".push")+"(callback(value, index, collection))"}};bb&&(bO=function(a){return!!a&&!!F.call(a,"callee")});var bP=L||function(a){return J.call(a)==P};bQ(/x/)&&(bQ=function(a){return J.call(a)==S}),bR(bo)||(bR=function(a,b){var c=!1;if(!a||typeof a!="object"||!b&&bO(a))return c;var d=a.constructor;return(!be||typeof a.toString=="function"||typeof (a+"")!="string")&&(!bQ(d)||d instanceof d)?_?(bX(a,function(b,d){return c=!F.call(a,d),!1}),c===!1):(bX(a,function(a,b){c=b}),c===!1||F.call(a,c)):c});var bS=bD({args:"object",init:"[]",inLoop:"result.push(index)"}),bU=bD(bx,{inLoop:"if (result[index] == null) "+bx.inLoop}),bV=bD(bv),bW=bD(bx),bX=bD(bt,bz,bA,{useHas:!1}),bY=bD(bt,bz,bA),bZ=bD({useHas:!1,args:"object",init:"[]",inLoop:"if (isFunction(value)) result.push(index)",bottom:"result.sort()"}),cc=bD({args:"value",init:"true",top:"var className = toString.call(value),\n length = value.length;\nif (arrayLikeClasses[className]"+(bb?" || isArguments(value)":"")+" ||\n"+" (className == objectClass && length > -1 && length === length >>> 0 &&\n"+" isFunction(value.splice))"+") return !length",inLoop:{object:"return false"}}),cm=N?function(a){var b=typeof a;return b=="function"&&H.call(a,"prototype")?bS(a):a&&bo[b]?N(a):[]}:bS,cn=bD(bx,{args:"object, source, indicator, stack",top:"var destValue, found, isArr, stackLength, recursive = indicator == isPlainObject;\nif (!recursive) stack = [];\nfor (var argsIndex = 1, argsLength = recursive ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n if (iteratee = arguments[argsIndex]) {",inLoop:"if (value && ((isArr = isArray(value)) || isPlainObject(value))) {\n found = false; stackLength = stack.length;\n while (stackLength--) {\n if (found = stack[stackLength].source == value) break\n }\n if (found) {\n result[index] = stack[stackLength].value\n } else {\n destValue = (destValue = result[index]) && isArr\n ? (isArray(destValue) ? destValue : [])\n : (isPlainObject(destValue) ? destValue : {});\n stack.push({ value: destValue, source: value });\n result[index] = callee(destValue, value, isPlainObject, stack)\n }\n} else if (value != null) {\n result[index] = value\n}"}),co=bD(bv,{top:"if (typeof callback != 'function') {\n var prop,\n props = concat.apply(ArrayProto, arguments),\n length = props.length;\n for (index = 1; index < length; index++) {\n prop = props[index];\n if (prop in object) result[prop] = object[prop]\n }\n} else {\n if (thisArg) callback = iteratorBind(callback, thisArg)",inLoop:"if (callback(value, index, object)) result[index] = value",bottom:"}"}),cq=bD({args:"object",init:"[]",inLoop:"result.push(value)"}),cr=bD({args:"collection, target",init:"false",noCharByIndex:!1,beforeLoop:{array:"if (toString.call(collection) == stringClass) return collection.indexOf(target) > -1"},inLoop:"if (value === target) return true"}),cs=bD(bt,bu),ct=bD(bt,bw),cu=bD(bt,by),cv=bD(bt,bz,{init:"",inLoop:"if (callback(value, index, collection)) return value"}),cw=bD(bt,bz),cx=bD(bt,bu,{inLoop:"prop = callback(value, index, collection);\n(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(value)"}),cy=bD(bB,{args:"collection, methodName",top:"var args = slice.call(arguments, 2),\n isFunc = typeof methodName == 'function'",inLoop:{array:"result[index] = (isFunc ? methodName : value[methodName]).apply(value, args)",object:"result"+(bh?"[ownIndex] = ":".push")+"((isFunc ? methodName : value[methodName]).apply(value, args))"}}),cz=bD(bt,bB),cA=bD(bB,{args:"collection, property",inLoop:{array:"result[index] = value[property]",object:"result"+(bh?"[ownIndex] = ":".push")+"(value[property])"}}),cB=bD({args:"collection, callback, accumulator, thisArg",init:"accumulator",top:"var noaccum = arguments.length < 3;\nif (thisArg) callback = iteratorBind(callback, thisArg)",beforeLoop:{array:"if (noaccum) result = iteratee[++index]"},inLoop:{array:"result = callback(result, value, index, collection)",object:"result = noaccum\n ? (noaccum = false, value)\n : callback(result, value, index, collection)"}}),cD=bD(bt,by,{inLoop:"!"+by.inLoop}),cE=bD(bt,bw,{init:"false",inLoop:bw.inLoop.replace("!","")}),cF=bD(bt,bu,bB,{inLoop:{array:"result[index] = {\n criteria: callback(value, index, collection),\n index: index,\n value: value\n}",object:"result"+(bh?"[ownIndex] = ":".push")+"({\n"+" criteria: callback(value, index, collection),\n"+" index: index,\n"+" value: value\n"+"})"},bottom:"result.sort(compareAscending);\nlength = result.length;\nwhile (length--) {\n result[length] = result[length].value\n}"}),cH=bD(by,{args:"collection, properties",top:"var props = [];\nforIn(properties, function(value, prop) { props.push(prop) });\nvar propsLength = props.length",inLoop:"for (var prop, pass = true, propIndex = 0; propIndex < propsLength; propIndex++) {\n prop = props[propIndex];\n if (!(pass = value[prop] === properties[prop])) break\n}\npass && result.push(value)"}),dc=bD({useHas:!1,useStrict:!1,args:"object",init:"object",top:"var funcs = arguments,\n length = funcs.length;\nif (length > 1) {\n for (var index = 1; index < length; index++) {\n result[funcs[index]] = bind(result[funcs[index]], result)\n }\n return result\n}",inLoop:"if (isFunction(result[index])) {\n result[index] = bind(result[index], result)\n}"});bq.VERSION="0.6.1",bq.after=da,bq.bind=db,bq.bindAll=dc,bq.chain=dw,bq.clone=bT,bq.compact=cI,bq.compose=dd,bq.contains=cr,bq.countBy=cs,bq.debounce=de,bq.defaults=bU,bq.defer=dg,bq.delay=df,bq.difference=cJ,bq.drop=bV,bq.escape=dm,bq.every=ct,bq.extend=bW,bq.filter=cu,bq.find=cv,bq.first=cK,bq.flatten=cL,bq.forEach=cw,bq.forIn=bX,bq.forOwn=bY,bq.functions=bZ,bq.groupBy=cx,bq.has=b$,bq.identity=dn,bq.indexOf=cM,bq.initial=cN,bq.intersection=cO,bq.invoke=cy,bq.isArguments=bO,bq.isArray=bP,bq.isBoolean=b_,bq.isDate=ca,bq.isElement=cb,bq.isEmpty=cc,bq.isEqual=cd,bq.isFinite=ce,bq.isFunction=bQ,bq.isNaN=cg,bq.isNull=ch,bq.isNumber=ci,bq.isObject=cf,bq.isRegExp=cj,bq.isString=ck,bq.isUndefined=cl,bq.keys=cm,bq.last=cP,bq.lastIndexOf=cQ,bq.map=cz,bq.max=cR,bq.memoize=dh,bq.merge=cn,bq.min=cS,bq.mixin=dp,bq.noConflict=dq,bq.once=di,bq.partial=dj,bq.pick=co,bq.pluck=cA,bq.range=cT,bq.reduce=cB,bq.reduceRight=cC,bq.reject=cD,bq.rest=cU,bq.result=dr,bq.shuffle=cV,bq.size=cp,bq.some=cE,bq.sortBy=cF,bq.sortedIndex=cW,bq.tap=dx,bq.template=ds,bq.throttle=dk,bq.times=dt,bq.toArray=cG,bq.unescape=du,bq.union=cX,bq.uniq=cY,bq.uniqueId=dv,bq.values=cq,bq.where=cH,bq.without=cZ,bq.wrap=dl,bq.zip=c$,bq.zipObject=c_,bq.all=ct,bq.any=cE,bq.collect=cz,bq.detect=cv,bq.each=cw,bq.foldl=cB,bq.foldr=cC,bq.head=cK,bq.include=cr,bq.inject=cB,bq.methods=bZ,bq.omit=bV,bq.select=cu,bq.tail=cU,bq.take=cK,bq.unique=cY,bq._iteratorTemplate=bs,bq._shimKeys=bS,br.prototype=bq.prototype,dp(bq),br.prototype.chain=dy,br.prototype.value=dz,cw(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=h[a];br.prototype[a]=function(){var a=this._wrapped;return b.apply(a,arguments),$&&a.length===0&&delete a[0],this._chain&&(a=new br(a),a._chain=!0),a}}),cw(["concat","join","slice"],function(a){var b=h[a];br.prototype[a]=function(){var a=this._wrapped,c=b.apply(a,arguments);return this._chain&&(c=new br(c),c._chain=!0),c}}),typeof define=="function"&&typeof define.amd=="object"&&define.amd?(a._=bq,define(function(){return bq})):g?typeof module=="object"&&module&&module.exports==g?(module.exports=bq)._=bq:g._=bq:a._=bq}(this),function(){var a=this.math={};a.mean=a.ave=a.average=function(b,c){return a.sum(b,c)/_(b).size()},a.median=function(b){var c=(b.length+1)/2,d=a.sort(b);return d.length%2?d[c-1]:(d[c-1.5]+d[c-.5])/2},a.pow=function(a,b){if(_.isNumber(a))return Math.pow(a,b);if(_.isArray(a))return _.map(a,function(a){return _.pow(a,b)})},a.scale=function(a,b){var b=b||1,c=_.max(a);return _.map(a,function(a){return a*(b/c)})},a.slope=function(a,b){return(b[1]-a[1])/(b[0]-a[0])},a.sort=function(a){return a.sort(function(a,b){return a-b})},a.stdDeviation=a.sigma=function(a){return Math.sqrt(_(a).variance())},a.sum=function(a,b){if(_.isArray(a)&&typeof a[0]=="number")var c=a;else var b=b||"value",c=_(a).pluck(b);var d=0;for(var e=0,f=c.length;e1?h.call(arguments,0):c,--f||i.resolveWith(i,b)}}function m(a){return function(b){e[a]=arguments.length>1?h.call(arguments,0):b,i.notifyWith(k,e)}}var b=h.call(arguments,0),c=0,d=b.length,e=new Array(d),f=d,g=d,i=d<=1&&a&&j(a.promise)?a:n.Deferred(),k=i.promise();if(d>1){for(;cb)return 1},numeric:function(a){return a===null||_.isNaN(+a)?null:+a}},string:{name:"string",coerce:function(a){return _.isNaN(a)||a===null||typeof a=="undefined"?null:a.toString()},test:function(a){return a===null||typeof a=="undefined"||typeof a=="string"},compare:function(a,b){return a==null&&b!=null?-1:a!=null&&b==null?1:ab?1:0},numeric:function(a){return _.isNaN(+a)||a===null?null:_.isNumber(+a)?+a:null}},"boolean":{name:"boolean",regexp:/^(true|false)$/,coerce:function(a){return _.isNaN(a)||a===null||typeof a=="undefined"?null:a==="false"?!1:Boolean(a)},test:function(a){return a===null||typeof a=="undefined"||typeof a=="boolean"||this.regexp.test(a)?!0:!1},compare:function(a,b){return a==null&&b!=null?-1:a!=null&&b==null?1:a==null&&b==null?0:a===b?0:ab?1:0},numeric:function(a){return _.isNaN(a)||a===null?null:a.valueOf()}}}}(this,_),function(a,_){var b=a.Miso||(a.Miso={});b.Event=function(a){_.isArray(a)||(a=[a]),this.deltas=a},_.extend(b.Event.prototype,{affectedColumns:function(){var a=[];return _.each(this.deltas,function(b){b.old=b.old||[],b.changed=b.changed||[],a=_.chain(a).union(_.keys(b.old),_.keys(b.changed)).reject(function(a){return a==="_id"}).value()}),a}}),_.extend(b.Event,{isRemove:function(a){return _.isUndefined(a.changed)||_.keys(a.changed).length===0?!0:!1},isAdd:function(a){return _.isUndefined(a.old)||_.keys(a.old).length===0?!0:!1},isUpdate:function(a){return!this.isRemove(a)&&!this.isAdd(a)?!0:!1}}),b.Events={},b.Events.bind=function(a,b,c){var d=this._callbacks||(this._callbacks={}),e=d[a]||(d[a]={}),f=e.tail||(e.tail=e.next={});return f.callback=b,f.context=c,e.tail=f.next={},this},b.Events.unbind=function(a,b){var c,d,e;if(!a)this._callbacks=null;else if(c=this._callbacks)if(!b)c[a]={};else if(d=c[a])while((e=d)&&(d=d.next)){if(d.callback!==b)continue;e.next=d.next,d.context=d.callback=null;break}return this},b.Events.trigger=function(a){var b,c,d,e,f,g=["all",a];if(!(c=this._callbacks))return this;while(f=g.pop()){if(!(b=c[f]))continue;e=f==="all"?arguments:Array.prototype.slice.call(arguments,1);while(b=b.next)(d=b.callback)&&d.apply(b.context||this,e)}return this},b.Events._buildEvent=function(a){return new b.Event(a)}}(this,_),function(a,_){var b=a.Miso||{};b.Builder={detectColumnType:function(a,c){var d=_.inject(c.slice(0,5),function(a,c){var d=b.typeOf(c);return c!==""&&a.indexOf(d)===-1&&!_.isNull(c)&&a.push(d),a},[]);return d.length===1?a.type=d[0]:a.type="mixed",a},detectColumnTypes:function(a,c){_.each(c,function(c,d){var e=a.column(d);if(e.type){e.force=!0;return}b.Builder.detectColumnType(e,c)},this)},cacheRows:function(a){b.Builder.clearRowCache(a),_.each(a._columns[a._columnPositionByName._id].data,function(b,c){a._rowPositionById[b]=c,a._rowIdByPosition.push(b)},a);var c=_.uniq(_.map(a._columns,function(a){return a.data.length}));if(c.length>1)throw new Error("Row lengths need to be the same. Empty values should be set to null."+_.map(a._columns,function(a){return a.data+"|||"}));a.length=c[0]},clearRowCache:function(a){a._rowPositionById={},a._rowIdByPosition=[]},cacheColumns:function(a){a._columnPositionByName={},_.each(a._columns,function(b,c){a._columnPositionByName[b.name]=c})}},Array.prototype.indexOf||(Array.prototype.indexOf=function(a,b){for(var c=b||0,d=this.length;c0&&(a=this.numericAt(c));return b.types[this.type].coerce(a,this)},_min:function(){var a=Infinity;for(var c=0;c=0;c--)a.apply(b||this,[this.rowByPosition(c),c])},eachColumn:function(a,b){var c=this.columnNames();for(var d=0;d=0)f(b,a-1),b--};if(c>2){g(c),d=c-1;while(d>1)e(d,0),d--,f(0,d)}else this.comparator(this.rowByPosition(0),this.rowByPosition(1))>0&&e(0,1);return this.comparator(this.rowByPosition(this.length-2),this.rowByPosition(this.length-1))>0&&e(this.length-1,this.length-2),this.syncable&&b.silent&&this.trigger("sort"),this},toJSON:function(){var a=[];for(var b=0;b0&&this.add(h)},blind:function(a){var b,c,d=[],e,f=_.keys(a),g=_.max(_.map(f,function(b){return a[b].length},this));for(var h=0;h0&&this.each(function(a,b){e.compute(a,b)},this),e},addColumn:function(a){return _.isUndefined(this.column(a.name))?(a=new b.Column(a),this._columns.push(a),this._columnPositionByName[a.name]=this._columns.length-1,a):!1},_addIdColumn:function(a){if(!_.isUndefined(this.column("_id")))return;var b=[];a&&a>0&&_.times(a,function(){b.push(_.uniqueId())}),this.addColumn({name:"_id",type:"number",data:b});if(this._columnPositionByName._id!==0){var c=this._columns[this._columnPositionByName._id],d=this._columnPositionByName._id;this._columns.splice(d,1),this._columns.unshift(c),this._columnPositionByName._id=0,_.each(this._columnPositionByName,function(a,b){b!=="_id"&&this._columnPositionByName[b]1&&(f=b),a},{}),new Error('You have more than one column named "'+f+'"')}_.each(a.table.rows,function(a){a=a.c;for(e=0;e0){var n=0,o=0,p=d.length;while(n7+aTW2I7AKPp|7 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
    TnB&3bpA+wSgb+O#8}j3YFp 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 F{|gpR%UJ*b diff --git a/dist/miso.ds.dev.0.2.2.zip b/dist/miso.ds.dev.0.2.2.zip new file mode 100644 index 0000000000000000000000000000000000000000..349c18d10d3a7a1ecb86f5a7bf71e7f97534d205 GIT binary patch literal 74955 zcmagCV~j3L^zGUGv~Blk+qP}nK5g5!ZQHhO+qQMuJ@5Z!GLzig$y8F6{bA>ql}cr0 z*HVxM0fh$opOq6$Ecm~Q|NDRi!UZxhbv3oNv$rv|b*5KUfdT@_oE1^X{%^au!vFz; zz5oLOfujD`ukim#LHyq+))q$pzoh>mLjEs3ZIWaYC}==H4kAE6nEwZorIVd46P=~g z|D)}H^!;zzc6h9mx5iWV-qCoMYsTfG{jJHGjM+vUQIy!5lHvi5O&#mXE2-KV1k$~p z1{(W)cOK4dTJ!`R%Aba+l-8O%W%7CdIDL|D&Ts&=Q9QDG9~hiF-P;>GJDWQjo8K-$ zxBO{M9MHpE&Xh&NXM22qBI+Q<#vtoKH1ye0)ga`f=+Ue!zR!xMKTeqrqR~S0jV~D>jn4ZN>OM9?D&T z#ss)Kuui~p{f9``DxFn;_RC+P-=vB0nA=`)ZaP3#fLR0aSm<>SYKla_8}dE)0mGs^ z;54QNv4BR^vn{bs#sBTtzDgu{S`dg7{zeo;EjR|F9z>%ZHj+uXJwQPu1n58_O%WJD z9JEehEkr}yVH*a{6*gQC(Ch*#q=XJAHBqX@K$5_?9SEQmRigV#0N%4tq$g*06&k0I-OV~e*9@GtsE_6DWg^I^C?3FtGxRT56jmTbuA#-g2 zTn*`u!U;)%m>-g=*E_4eD4<5sFuCY~6_1?wiqtVgqkD(|M{Dl?-l-5#gitM9zGaaC z^i&wHP+i4bt6bc!1cyot;uNXF&-R8y?LP!;j&F_l@~0rMKiR=BceSKd2~i)HrGG|w z-7+z7zLF-<;hEnuT4V-woM5SWh|XVX$WuU-rA3QC0J!!2LP|^wsoJ!4{>()WkQTs3 z1=^EG8~~IAHYv0wga1~1dA??VHCb&UR~XQv?6p)oLiiv#)@$VK&Z+ zO2Az(d-Kn~t21>s(kisPewV7HAy1 z(UO^`5tkv06f}nmdk=@ebqI7dCfk^-zomUBhPEj*^vhgGgK^Qu4LVDGO;Bfc=7VFs zTClgfU>sk_CX|)6DjrBzhr4l}Lu7|+>#bI2 zU@6qFH8^?$bbCe=N8vfK)Mb}JHR0r?3xx~SSI!U<{JGL2wTaPqp=O=nGYB-MpT37b zOH}~#C@nr?3ZkCz6FH=?cu;~v&kndxZb?;2=G_6uCg#G`s8rW(iz^CLI`N(*JVPHnzIwnVvA85NpEhfB84pO!DikOOR1cNnZnH|Fn<{wp}Qwx zGC-k4umd8#jDSk8Kr7pUbHZ25kvl1;Go!Oc#$JBdHbzzFJ7|_VT&=$Esq9vwiGarc zyXFO^^&K<7@D=mpuGx$4jNhn*CzKlIG)*ajQ?lb9?&cUm|4hYjuxc6(XMz@nmiUjs zDI9l-7~9Ul4;7#8L;|Rrb`<1x;8TWvVzb>WcDvv)4KJS!D1yd@?=b_WJ|7qU?ZLUU zm9vT;+%|0%7ZTM|&DB!m)ivKq$G2uG&yRtWmz4LVG! zH+N1nZKJf_ADrJu%T4|Cewfg&X<~i(Fy8c?wxEA}S8K)%c&0TCy4-!XLw#x7++F5P zy>S~)cP6dwuh$2gIeBA>KpZi{bHce5HZLgTwl|j+LwW<5AXqfQJ01LSpfY{}DyQ;@ zDe#f|FXZ=4RM1J3vnu8N2mbWo^WijA5)eVk)&8LwgPoA)ztC?IY_NPTG&vO>!7>?w zgBNx@d%NrFzP`ZDKnUQa{CGiNXO{OJoq7*B`-)`J>)W^yGZWE2=+Ep0eT3gL=k&3H z2}5@%Z*=NaZ=o$9Fsj5<(>=0hmq67rDIQsOWdGRzC2KuBYYkfGDi7M(I*aiHsu1&Q zk>?RkLr_Dawo4QR2MjaO^ZD=e;zekXo^hl(8}EUv_<*5FZUw46p${;7~D(C#w=vwDiJ zWoK2$qv*QA)WUyVV*x9W@hv~TOr@<6V6k?WbytK4vI6SyVWV{0AMA64_tU?-wq6*c ze*2t~tftFm( z6Hna?-p!|RI>B1&*@Gij>j!l(Rd?`9%6I0dx?|s|yAzZ%)PI8BXM8H^9yGf}{3ju; zsXII}QA&t4(<>xkjGsaa0ogac887&Yz?ygbO>ot_KH{B(XWe=x{T}wM z+QjP^vp>b*pW6M8W@-NXwevU~Fr|!W5H;cJK856kSL*0&!y2R?89@s^V~dm$C|lBh zCi7#j=p|P%t)*C+Hl+}d7br-bP$oUHe_x++*`atqoK2jbSVK(XBnVJBJs)>$1B6b) zs4|gfR~d6s(H$Vu9-ZYbI2tv?E(%&eKi zU7NFi*_eVCH`ilF9%p2)zHFrfy-EnuUf_{gG%Q%DgG>I<#$>SAi}?3Sn#N4Al1#4E*lsq3}lTd%nTnuE0JNV zdIel)$ocEIfDLK{{?1)?Jehjc&!z%9`kbrY&Bx;EhdI0)L&!ke*O-~!2P)KsoA6lP zr#qs%<$2Y%J~%;A-T>F|CXa-pQv(oBikX*3DsiJ1Jg%%UK`_DpU1yk=$^7SRW_-5> zS0!2ta%-*Il*iM!3yfPjMYm?J6d5c{MfEi`AUB<^Tnh1m9!yZrW(Mmh|A2^nbX@v* zu%XE1ZEVk|Q)STOT`8KQHAJ$g-{2jbGF85w9f>M}G~m17(O+a%N66z14x&Bdx?q8| zt%FpJi7>Jai4G;MkMtecGMFWHDTbd%425Q7G@#G&t|br!xuV@U#8%SpuKXvqM1quxLPn~GJVo60?~1+4DF%gY2Axpz_}6~k~s9~x%p#*#?#jKc?8aS+=E zOqGhWBeA$1?CaT-;8hm>(UdLniF?YZo9Z#L;r` zFPux`e+a58{$?Lo$nL)=^`_!o`zjIK-KW7Y_P!-jn;~&RGx=NBAYwX#qjkE1uO9-s zn5>%KTXl7X41&Uq2#^G4tH%c1@{erwN2E}mXfg+7V1bCVFS@#}j=e{|_mrBi>e+Rj z*0M&Q@tSP~Yl5H=;H~Xs*v-EaIO>`dtRGVke*HEX=wD>gea64ITgjB}j>tB%#Awuhk{A z{cZJ~<~1+A&zD7;6=OJ+KiU4@FK6?8P}P9TXNecD^P#jRdbm>>clC-LHbU#QRWIOV zU-sXoEa#O}|HxGqKVNs*~|j$J2Raw0UMa!AOJf%WFuiQj=f_ z<9rS2E6X-Fk~j*bqQHI#a0rygX%4Sf(J4wK+*J%c&nT`Qw=OuYt9F}_)(Pz{uKU%J zpYrO$*embNBTsJD2(??VT9}j=1fonB@>VjAY?ztDP9DcuJ=KlvaXf0(rE8z(Ha*&8iL1?Q1SdsK@cPQ#*uY>P?>H(f5jn<{VrhIMMH3wZPSkC41D=O#U zm~;Zda)e3Rj@5@&=1X~%DX3vMxhF_}n5#8cOC`M6?Na=$HUb&`_W7gE4vtU%hIZ^d zyq9r_Oh%G&5t)DSM4RauH@@#s#HDYh2!(ZtMc=~U0l46vmBXBr8*Tkz-X(bPU9;Cb z=L~A(?JXBM>nW!GeG)aFuE|Nyp(N)E99(xNr4?IvM@9kU1F`hLaOfeuYUEokyyw-M)+}m?UulCBM^t*cmXS%OHy#ZtVhZyeh zS16Y=hOyN4Yi+jv(K=f&_E&1EWll7_AqX`XYSY{lr8P0QGAwS{LR%VoELt+xc&kjk z&eBe|boUt>A}T#mNfr6ij{Vrz%y1PN@^(J!zSTS}w_#q5?P`fQ@(syPr4pFljKR1r zX>M6Vd1~o5J}-wB0gamHb10B|R2=sze1kX*&sE`u5L80yWCZYl zFsM#3mbF)B$pvl04>5|Cc+P)bPY$}wL<>3Hoq>^VaEG94K4H%81?K~hT)9OPIdb7^ z1UM$}SAka$@)y`&%VCy1fG2v~R!zcl=a9@Z&KJ&h8TzEj^+_k5rjhY~-O;3MXNOaW zoEt1#w;PpMwI;w{vW}K8gKG4mCB~i)=xQJiQZk$8aG`jdKnOe?;$ZA( zHpx0A*0LLh?sh!fEL7P6Q%dZag!O%?A?^K$QWz;F<5@aW#RgMycxX*KOaU_kaO`LF zikbKR;78*eR9el0wFN5Oi+{`kH3h9NZ+zlxXYA0c7OoZY`AFE6Ly?z{1g(VaVUWqB zhi(FVxSrd_-bBI~qI$Lh!aTuu7@vo{6=#{VxjP_Mg&r;fvGh!qHP1gwAW+qq#x8c} zFt+wstowwdj+g7q^qR$K=8Txz%TCJ}%ncO8kfU6K)?C^k#KtgXG~|ZZW6PI+iW$f1 zRTvOY5e-Ubi==Mt>!2cGR!~+WyCN%e%n=mNtwqPIV_Ah9ib7j1TMRuZ-7YUc5G3|4 z!WjLwAIEyi^au8%Y2S$uol zL%ri&3V~5wLz(Y8t3M_`g_gpjE-MGm*CQrYk@BIR&5O^H)C0%-x1(-Fgo9Ig^0T4z z=4`=NtzaT3h$kXdGnvSAbkiVMR(FH}64m$osO90YTx1m#svamfwhom391@#pO0j8a zxTH@BXuTRh_aKC6P3PUwkvOkh@cj)9_%uOE(}yhW%ND8d9mZ7hqbBn*7Eubz^8{a_ zv;nNDxU_<<9Y~E@EyR~yKw|+UnC@S-5Vq-hPYh??ore~Hzt8LI!H4ZDIyd>m=`Xvk~SsB+|$h!21hpKk8jQy8kC%j`gs4z;{&JOa0 zOHSqv5B*3%it^Lp&7T1}!Cj^jVOMu)BAjKhGdU{n#mriNQ$QE4CKqlY))T(_{wJ>e ze?wNdM+fRQS0Es88~_m3{~=_xwlgtwGXMWcS*Lld+_u;n_dcl&+9HZIQm>yIV*YT; z9zHL7Et$zD-eM@i5h5jOC>KwL*{rO7-?-rcLGm51Z|(55mC2GAFr;Hm<0yp5yY?c= z4{w?L%9z18crd)#+Ee7xKYF{c;EG8C6$M!|@`#qNO<3lHK8e!h;^Fe4`N^X*(saRs zIFJ?bMBH#;iCz__PjVkTI>@gC-UnoB7O2gC<6B<$|M!$cZooKN|nUvMexJ}bFL|%Gg%QB zq!&^Jm2L=kJUHc%2?|6^ZMY$`k|xOlvnFJ^(H?I|>7eW~M$g+U^+PC9LpP!$QWY8{ zKw?Elw+=k|l9V=dfx$K6!NU{MPj52W+{U06BM7|UJ_${V4hN_g5uv|LM)lp$8Lf0S z5rF=+M7KS-0FutwzUiSc>D`G~MFJ55tylz1Xs%FM+5u@oC(88xhri4cTniiI!bhPH z#>DaQ6X@sZGaB(6h*m2E>2PJ)_6S+TkMhz+7$3`Kw}Y+0A(m+^e~fS8EwpK zyrWCbUS4+%HsP#F>|;iW6qM8MU>LL1BF~dFF64cv_(;U~8gqE&l+IA9k|xW@Jdl<;x5i@6-f669RpTMu}wZek_qTP=7O! z5DfFgXX_|U$KQa%8WSq>s-MF0Yyz z6jY{*0!czh)%iO?50|XMDd#_m)&9J#I6wkT@sk6&8ahx0{^8%!@jP1o4>}{6^HLZo z09QhMKEk5l7;2a{|1IX_H1?z$ybQ6z%8H}@)4sXSt?{W(9*_6u=KbPEb`2-it|!6G z^-8vrKGwjF;E*l)V2>uPUy-Q`jNE-n%|6Pu`AjwxZgYe8>-piV4UnUWS6OW|xS>+3 zafdx5VP z^4J+j!qB@zcbn?bLs0W%v}q4fhG#NDpYQ;WrPeI$454JG^snh37q|)?I*DrrNcMO> zsy+qp3#eu`jc_!KbjsduPxKUf$c4)^6Y%Nd0++S*6L?>F=+b*F`ZO><0_eAHqV1kU z(>;RpV8*nLhOmM+OC~X(h681WuVHd%Tike%?pAQW+}obC#jhK@T)j8^O&)E@UiX7N zKkUIP0Ai(;+_-SL*FA4HCTHhWS+Z?z+>fKX^G~Dpv4uw-`nI7YB6uE(VfzQ-YZakcK_`Q1#2zND#Vw{6vsas3H;g{63eS1A1A3T=t|?D|WQ=<{kWW&3*%EtvsYw|xV2CNDR% zAf{>o;WZ9qvtNdW^Bq_SHP1($p+Ej8^9S(Sj^y&YWXf2Sr+|W|;;-ry;5hGXq9GVn z_Dub~zM0@e&G85q%>2LEHPXJYU#sQfjA70V!@6W@@I*E2f2!DXz={5JI=4ewN~Mn+ zSe86;$pwO1#9c*1wyvhqfW?|nIVBa;8$PB#q^&47VnGI}kTQ?GL~j`SlW*&K)ZS6b11UL`fQupz*B*^@6n$ znTEYVKY|ksxC@W}7r|&}5LH5;ye$rOH)S(t-;aBUW!ny@&N@b7J-R^@g_aY4Ge!&2 zPJxmy6RWoWDqdMn&3a0m6=B@ZY@m|YAb-F3XmhVWNz_mM8Yeifd z-PI~Mh{VJC;zd2NA9lEtnN)*;rn;ix*dZCQ&LDx=aLXO~sCvecj9O6q!Q4;+uY?DL zdX*HGYt;mPGLiI9oz}n#v>qjC`DD-zVl6`=N3TU!&j%Zprx$rPD%~p2)M+w|G~c3# zSf!oe3zv%|TJ3+_=f_0DD*%J zU1+n%rPU4)d|PxE(9aWwXzCHrz3Q==+}$RV>yJ=k8A+AWvaiu^rRz|xh|+&0AU_my zhUXGLL|mlgGx;K78~tNXF*g}4tdWC~enhAga5B7)Z2%uP5^7*ydChsxMy7omAQbe_ms3IA8+N1QpYxE!7Z$c!aotFK3EN&gsez zz4R-4C|&)RQ^R!m_#{>C-=FnR5`2!gSBn_b>pOx;w+D!x`C?fTW<Wx~e2B?x}UdNS*m%~=lQB@R@U=r&=6b54pZQxL) zs-?Y5nfXbDQDkTw+`ud(AA zwWY}9*J9full1*riyC)u%3yhrA&i7ix7tjBtzzZ$roZFcd(a?wts%HOu(a`T$(FZt zLwb#itRTeiWRc%UPak>})(pWJkxMWN!*T=4*8%;QX6Iy`^}NHyI$uv-?9e9ZZ@=Td z!xhK`1$dqhv>W5~io(||P9cNItNQKBToWHX=DdDO7|m{da&%;GXpR-Q0*-z-*FG*$ zNM`Hh=&!@Gv871QVsaRE1Zm^ikS9}2IY8H`Yk#}GYl>ITKLzeH2XmCVw_t}~fkawk zojhbu6A}zDQ%GwdG`ngKj7PW#R=Z)5Z9l-lNTfdi=RADM?jC>%8OX9c`o&{t*+4g< z?&w$I`W8Fhc?8wD49BRCL<|p zKYXP1^_wku%$Nk&S#M*n7jtpTtNwds}&-ORwzPC;<5629+T%RSJach^Jkrkj6{OVl)I>ynx8`v;@XHVVmt(%XV z52P!xY_qxxR*O1L(SInjgB8w?ZpqO}vD|3P(LEuAj#|(;`*0<4FA)i}VPil`=`E)A zSJgHChOKaI${($@ZeNyH-OAU8U$8<2mZpPjkXj4LHUX;^Womd`9``{AL%*P4Oe#6O zid|B?a46@Tmk>&M<&TT$E~0wS#b6Orj>esi=P{c9=C|V4Vp|yXxmQQMUo^H{+m9sH z6a8vR;^pwbic+|%EC13_FDv)ygv9PN&oW|x>VvMBciwh|zim~9Y{Nu0t_oe;!qN5mRUd{T2vCd^^lZ9BI#<2cy%<5qjYA$PD>d34_>fW<2lE{ z7C}ci6=$j>%PURTG{>lB!BJR)t+h>sJFa zf-M3?1i7!3DR#ZlZ?|#Dsb6|$jKzvMWBw28EW3G263JTu%Gl{?AYCl})#(L0vvlwW zJqL`h$Lw%j%xBs?DZ>$|I4H%-I>znI$QkVk1;OJyLYXuD*${F!KuI3KDp`bNPmmS1 zC1`eoi=?8<868`~zp!N!J}K^c;~j-M3e&(S$9op^Rwr>#Y&e^CU#6cEy?ZO!qQA&2%JVBRj6LAtTQ$r zuGm)X2_b%2MtZ)57*htUg%Tn*Pv&YI%mM&83TXYl53qEn-EQ9{-0&4xJGZV7i928! zwmz-u1DKd-h(=$`c`lZ#gtik^I86{nODKB{ao*aI^7;JOceg2t=0Mw(8mZIBSHUR}=ZP4C8HPnH5T z0g->T*kZmeQ57lUH5jb|XF-F-R76dPRhBtN#99F4y2QST03&{EXm#T~KZ z?mBEf%P@ zk5f7OAn;r128h_sOklq!5jkj}8K?pZX?Xk~=%QtvX+2Sqh_lVH*I-ne(HRWeu!y~u z!m?^kd4|W+1YnSWSCY^|qr7?;0YJCVHKr`apeHg+DrRM`v!`v)vgW&I#Ts0ykA$6? ze-I+4yhon19<O-_D#ba6rX+HR-y zj12ZU4}(lXrbab?AtPyUGCQbnejmWF(%OOq#JZp33q(^}jaoaaU2#CFwfn-QSEe4Y zt76*L;G7)PDLg!gTYuRttFtnn?+vlDvt_)RXO+PQbsSw=Y%2mYI>uW6v1(Y|))N(E zQ`YAow_L8Db=DDPId+0ZlpHwJN_`y}%06L{xLpsRS2MUiJf(x9v5=yBXiz;$WVTx2 z-_Npd!pn@>MJMfU17=fEuLGbs(TmqWv#TENb~ed+4ev$>yx5u}T_t#dYokIPE2+{b z<_fF`Z8KBaMfTx&m)!;Q_wnF(dl9^b^tAHgdb{<{sql?sfudu6y4st^JGV^H-+eCh zC1>a(ua#mT+rg+rhX~IZIN;6ibD;aQvL-_9)w(K8E|hz7ap2*Iv}QjwOdKE#Q~SIN zd0k5BLW83r@l0)?=j%dwa^u|~=Vn<=jnwFxZ#txfri&%b# z4cLB#VXPf1GcSIEVo%Mx`Wbd7Z}^(5HV0?jOp2iFCXScp-^k8qNeg1_*{i$qxCQ@b|}zD*>rS3P`~ZxSI#>Z z0=<1H%FeVxANrPsPW$2+@ATh)rl!zoyu7>ZSRjhZ0`PKL$IX{i0{f~}{xJuAC|a-8 z-TcgoAbnrQ>q9T#NWdbgir;a&al3z_Xt#{~!z^!Ri@g`+Hn9IlSz#ca8n>7~kHvRU zuABpWnmU7wOk|eBL3agt#QitiGr2CeEX$|~5#WpPMz%mSgrbOJZ#z#D)|^n>boIl0 zkb-}`=zPKbY;IXmb2ZmJmwzz#$zbV5S2)_4dDN(#(-Ct!L4rzGQL!2#)qE<7icu8`s(m~ zgm*yeSH#vt%r147{>df%t=qc40DgrpQ$VFc*4EE=l|6ngl&@gGP3_H(JDr^41MlSLxm0l3Q|Q=bItxeY|8_KUZy|>=3dM@3qHi zsD`2suBux+gsH`JZM zS6eSnbke;BZC|EE%pw|7ES3uC$Co375DD|cH&}Pn!%IQE4eU*{=M_%{M%fx6>85t@Jh*_Hy-~@QeZ6e^ z8^g~qH_v|sbF0wFFcjusN~?vZs5+M!*)b%&g*N}S?(dh&5nrX+e4E+_xaPk5suHLD z`P3Rdp?*}_IuBXjDAb~P*Ms{BFWZ}WBL^Wn6|Qws2)M@5Jyfu}4aP5)$E}KH*6RFq z0%U$v6A$J}b-C?~HXF;YR@{EU=&g|SWO};fPh%a**`Q>}Hw;sv2#@&=llx=g)GOcC+>ng3)xHpkIyGVjB-`a#Xw0E*fOZ6Jolc zvLkR6G66So!sehYV^&mI!v53L#fixp-XjhbE}Wrxj0@%9;Ej)~xxjfSQsxLF1mB)i z)XR)`k^lEk9HA%&az~CFcALZzaFLKxs^E`&g_7yo*h$(Y7AAw5k*{;j9jNCkc}Izfltlfkl=xG~xe5l1 zn&qvB-DPJ_O8BA*f8F;MlyyO^H97JNkQCX_FLjJp@lK7M~2_i`gN36LP@z!L|r2B+OEHg7|m^nbUkrpu~A6PQF=I$rA zo3E3gNF<6<1bXheTcl0RHDF~*t!MnK z8biC^HA6j(1-BkGO^%&Q%VdN|Zfv zLRgXEt|*l3c-dk^qgZZDs5HJ_45jf|g{j-_W#Q zz%edak6O6csFC3DhDA+wl=lF-UL!qcror?xDFn`S!6!}cH4m>*UUfMI`r zf~{uf?~yB`V9TQUpP5xl>-}b63+48Fv7+NWO1BIhhu*c;NeIu&CR&p+&& z8mElf`%`18TH;F|vD(C2CO*<4Vf7cmEaKQ@A~;5){j`IPsUrm7TT-65R>gXy1u0cU z%?0X`;lb@U0{0;OH7ZZH zb9!#Q{rx&R&!{K6La*FDrB4how2-ul^U$>mcZHuwGEYQid&Qi4-?KuHFKnrY>A&D5@~M!6C_Cb?}$`q})WBd$F8$ z3TpEWjXO~7@-H66TJ7C$T{rzmQ{M1qYl+eJgp$6+O@WgzjPi%?_aS}Dp!3@mWtoSQ z0aGvIT)Q5)nQab9PE3Wn8%UYWJH>=)8RsM!3i^q1QbKt?L!D%<0gNHjJOu$h_mLw) z@C+W(HRQo7$e;YM&6%f7JO017KIu0zYtT9(0}50c*r^rRgaje!>> zlnVF=Uu)nja?#l*O5pQ|KVgzc1ovR}>7_&aYc;}0#5tnh!Of=|H)%km zhlc0~Ftw%@_Wpx|@86bkB~S_4)mP{bfMhTzO%><|yfC5yp^WIP6nV{IyOvmo3OZV& z0Zdg|x?}AJIiy?m%00{Ljx30SN3^gwAUeRGh^qG#?dC@uX!|!vTUuA6v@c&XRu0I* zMGQizhju(P<>23&sL8Utgs;Uec7+He(2^zEe=lN7ZK<7KL7;%@Ji`??1tZO07Xa)i zLLqb$V7(VC{6gD!u$S>q^rUp5CR429-e#9qJhq>G0v_hAJIRRKKS>>I5gc>P`s==f zb)l%VVwbrxJ0aaH8WOtn6KBV`5KcBVBsenS_MJzbtK;3_X(>ha|3=Xj->Z(~z}7qw z{^|qW4Z`9o2JQIlg(pW~ue#8N_Fxj>zOWaA{!9tiSk011ZVu(NV?gx~H0(`#x}9%= zDFO||#<8}t>Cn;vLC%GzM}>()K$UhboI!98aQPC*oi4yXr6wi4CvY{FXhHGkneo*DP`*z=pS zY-rARcsu%%X9NMgYoddm-$!;bel+)#1mA494kF>b5EDmTr4!3eEuGunE_(hw+AHkU z>DzYytHonQPQI_D+?_u29zW=@{aLYp=lw$t~axyy}@HevL>J!4CyIjeZXWER3=LXF} z4Gaebq-Aw23zS!XjlyLwo)N)fMN)a5U#Ija(Ayj}`WE+0gKP;1-)QS7-dMz}C$OAqwDPrb znA5yeFtS@L;{pHD~OXMnZ5=qhLOOFy3KvEL>FE>aHZ>tUAfC z@JGD;rx|-OCL%d8flY@=+01K9& zMSihHOZe8u;V-Xx#jiV~*^wOYtT%EQ8m7NR?m0)XOg_+u5a-R=uv!L3P3%3lzWiNV zbGM9Jo)DT-L-c6&3es>e9EmhzhIp1*fjI1(Acy;GNuqcOs9|Azx3KgCuPDmII^E`Z zvsLP>x;RLlvAjHJGgJX`4xT3ktNfF&1R+FO3?q*@dAHBXy|Om&Xb0aHcf`*P<9{CVI5K{xo$o&gs1( zpVBA4`pfOrPHq|GhbOO~xi$ejhbIUCQIl`#R!JI%ZQQBeq#HR@6NC@tQcEu}Saf_3 zob!)bqcGNCqYNevj=|V)5dneoWTgVpC`b;E&*comIh)RK;wJ3@v2z}|R%`{lg$LeW zc^qJpi5b1=Cwmurhl-=an${^;G#%gY7*hn5)=?EMg}4hSWM}$KWIQr>dO_R2>zE_v zLbU#+rt}=NO^&Mz#6K7Aeung1~+0{iaSCO)G z@bHTKDO`0@R+=nTmxy8AZjDT3>)MuC@?lIGiKlO*ztO6I zrGRw+<_Fn08ncAJ_BcDgw>Y7(C2Tf&O$|017PgV&F!<&uUwRb15(shUIT&9#%+!Ns zvoihs7l!a@I1;D&mCX260R7`baca*+(2_=?Qf8~`#`hrv*!eQhL?dDtFEeo<&p_4c zpj>*##cpGQnSBpP2DZ#=R|)LiTTKd;Y$Cs0hTeE{yErTY$7-HU?|A znmLcyDxlMF2RxK4q8=*uha7t-4E(R{-j3D)pY&=FMVSUq`nMDgg#^YmV;uT?Gt|*U zrdb-==w4PY&DlyaJuofE79``DtoJuh$=TKn4cMnPdsgePEV=8Bwcg*xZIuUc=3uj7 z?NJ=JsYs~Pp++*9X;ufm5DL#Kvc0$v2t(M zDs|e*>G8&R8 zL*??m*yXv=gYTbG7S%F?*LvTQert*VGZwScL9`=-L{0p$GD0#uj;XHBJ9)>Dc#B7} zYn`n9of*Y)hd4tEFEiy$NYI1X9E+BACvk4u!yJYnprNkng?PT}+0XX4#qj3(M~<%g ziaD}(&O6jbp~2I|G!?zGZ>Qervm=tdIS0t`V=Xf{5J;6L43b!k+3Zkd;hAjM`K^r+ zIRgmgh8#sPH;FBy^Y0wccc9h3%OV@=;tcur{G42JY>6|2XUOmQL;Kg)e}mPux{?E zm!q~RB=+8km0V@xB;t$c%?jc~;U<@jR1BTozOj9`cM*F6$&jHIz9&h%=8|k$v*ctO zf4#ghcP7@E`z6`SUv|ooJdrE?iyk;{SHbEgh1K(&RiY9=G4&qf?b9!)zjR0|Nz34L zBw~y{lbyQ;wM{y8YH{~8wE2sA#8=~^L;9v!{XdvoJ242Pb@Hj1C!viHs#N~wWQgErMDP4 zErf14@s=}!7mG|db;GGPkaJZ?!q)`V{o=pe^9 zvoK11mEYMa*>=1*MVU3(cy7xCR|PnB1-J(%MaTG=5Tj0cdQ$+2?C9^7&vu?U^1)Ul zY(Z6pAhX^>7r`*g^}ZK%(=lQEyEEib$L8Nkk|wS`j7#M-HUAN;s>EuZ=z3*c4lZi9~J_T1YVglu?|C9y%%gWYV$Z>&r_X{ z4}x>pqnzmwUsxFs6U+vFS^gyt2F*Vf|GLre3|>N!{MG$8^c+EZ-X&wZ=t=p&msVTC z6W&@9kguf92G{<<&R1NYdMhE4JWCqArqTwtLwS1QA*ra+&K^rm{`5OSb*d~DM;ClL ztRm9`pLXs8>eWm81kh3%QiRAZiy|C~xm=yXHccblZG3R`U*oie%!pX#6>ch6vIrtU zD69vT69Xw5Yhr#>>cDP=j?Bn3mlI|e0k_d2)b&7$R4Zl#SPaNMM6g?m3FzP$B}wQB zvF~(>R-kmuKOMVW<QvM|fc7WT&!MOh9%v<dvenF!xNC zF)VWKrdjUWl+lxcMWmnhLuaeiDPa_<#N2G`j0DlMGM&LesUu6(?+Hi}Q`@S!jE0Ch3L$4!Ob3Gg^_INQo=DjzW1v${0KrEQVuR!gdaaiCFJ->T*f=dE z)9vV=rlEt3i30lD-d`_jy(F(FP<;jXGjk@NbZ{wTZm_e-R0?SbVZS8?u0SCxt z{Y!vk4pd;w{{=rlz`rI+iy*b0lQtokIu@E3$xWXCKIi3FzU?ChgnPGAwi3vHHmURE zqMVZ8`^9uQ0>X1qUwb0s2lAxo&N zC|;J;QZ|z0{V+~pZVc=~ez5y67@?a#f=>o|>x>rx9j$&1Z@Jn-P~8nr$Y=#URUI|Q z_Yy)ciZCX|E7gmAIYX=yw59TmgN4z@HaQ3M2-@l}$i9|>`8XvY2S+lwM22ZPy>j>~ zlPlTJ6cx$Z=)9XXYczreS*_(X@yAH$efL`(3A#kum%D4?H`RG}>LnsbHz{E9lqF>N zM~S7$4!OJ?+2Ayv2PpQYtb79NsoVKSRh{*-SMcvGcU-&)1^2A^M)3wVsq;;KKE3Mn z*3oz_|AiVMG)OvquVs_cUg=1$G{pfo$5F5rae1-q6S)!eiR4BvhWba6io z73_H65KTM5%&y%0>$ZEe(`D|jzulvq&gH$;c6U2n_Wi%N`@7igw7pRF0q4~o9#;P< z<`te+`($`9rIU-LG4SGUz+1r*yY8;r<0@EXuH|pO^`OD;V(+!z7OOeKEQ(bJ3Qbye zSmWdsou$2El30Zl`fi0-E$kMzA+Zj;fyI3T)-CJ$)w2FnFVH;+*UC``hM5?rDd~4{ z7wF%|{Dp4s^nhq4Xd=UpRgG_vLkw4r3n$rTakD{Gk4?;QBGYvBmP=UYG-3is8eDPR z(0Hd|bsowmWG;s~CqoW}#-eyKab|c?H)(xk(dq@(5m&t;3Z({k4*ucr**w|0vBCnBL<}h9RxE_%(;=+T!_vre28r^);ZWCCHI*Mr{ zB0F&zpGQecyk_3OzB|2U0-JXQuIV>~d%H*_F{9VisHgub5`G#D(k==)f&HRrj%QoI$ zPBFXjaH9cW!RD&Ji%~c2-DdEgaF(vxy9`dN@zrLNpjq4ZhPb*6(2(*+4d1xGCD|%2 zXLvCo#-vuzI5zJ{xscL*Bw)_8 zD}Va6i3Q+RM;o)Y`TC%SZ?!Hk!+I~`IKwY*TGmdSu(RsOWD^)lxYkyhHQW)8WC~^!3je!JFgW9cp{_qEFD~25~ zKXK#fmoLA}_TW<-`YJZ{s`35}Tx=9Bjh(9H2G<&IM_A{F4m6XNjEQX%F}Z(bGuGOf z5W9#l*=ezOg^}$SuPV7+kou*7^>A5E3t8+}xM8`h;-AYB5~4U9VZ~6sK~}@DsW>_D z!!pB2Gzmj-E-Yy+?izQ~Cqo{c3XIv9mK+F@B-sP6$?;#&ve_??jm(?ZkV;utNc3U( z@aox=Tv9--N-()29RNn*x?&4Zq*h1!_kf`O5h&{YW2eMtnuKcUYnH&pP!q&BWj(dV zMc;)i{mhu3u08Q77XpX28WE#Slg4;5bA%$E0x{Cjw$??rAv<1+_Q`&biW|yyzCa&O zTP>0qUi5S`Lu2EEN-6(~SQ^W`#J0xfwY4^u*MT__b}V5zoRev5;=}22%6!-i!E*X) zp3i{5US;1_6gwS%qZXPsnxv$O9-NFW@^Wg;tr%hUMDoN2vV;IU79 zt*W(kI*g$;w0j?z^{%vr_G{Mdzte_49rrC==jmJ2>GF2-WauUIE=6fP%iswjmC&O{ z8U>~fcIKgn4yL>dWYMzWr@&>utOwa}2ia|w>QbP$c+(>8{2pz){cM*uafT14y=WsV zIO()FE=#9`vP z)6=z9%69gtRGNM*KUp@;K2+ddqY?Niy*oAUpjZsbv*E(R{~pSrv4=+G(Xt|_X6n&; zJLz`AYd?RKEpWHbx`HWP)DH&daJyWd5~TFUulFAe_V)es}vVzRr`Ej}ynI;5;!QP|Tqr>ACU}87W3Sf|L z0A7$MfD54XMp>sDly#Z{LFla+9mf(O)(jkfS0lL9W$YBOoO_m6780&Zgd(26RRtFD zC5l^Z$)0ju9c@1yg-7VYyQPBT90wr~$#ov*W3j$+tn*@a*#P2Yb7K&}MkPsJWSdI%9 zS$`7q!RqxwK39q?53JiR4o5P&#-~Hrs3S~#3|Aq2h%ZD@j3?kvf#kn9Ft0qYMHu!d z4XY5kI6dYd4Bo%V#6d+^ZH~ua-Jf)&OsfX$kvUdKFgh;J=uW=~UD+AJ!k)auT~TX* zYH__tzPon(LR6N=MBq73gv;_c1ayfqe&sTL6=(d)<=X712o{XpTQ1bRh84?0LXpzS z<*bm4L1jkOa?19%%rzP9c>wc)i|5l%4_eoAV4h=?bY^X)T6HR|;wywMSqi#^OI9A% zEZH=EOG}6@3Bz$(p}<`T519ry-;dNl_+i%}4=)xd;`+-o3EzRAACIfkLO?=rhZC3b zboxz!hOK=T>jBqtu7Hzp=c|2j!&|(?h1de`gF?V!KF2`UXz`O>!u=3!?e)Hjfh%*| z3KvCPSWqQAuxdE5%xR@J`O&0Wj(1V0F|UD(D>S6Yy$280EG9c`x0D=3D{F|aht4Sg z!@Usib~>cqlb9@Y=|Qa0p(`RatI=FSmhl7!el#y<=$NZP%isbAWBZZmf(d8l+u3mM z`lIc`jLoMtMp+crZ~aaS$wZcY5IZ2^5;jsXo}s!z_k|TDe_t(%hp_g`T5Y4;Cd!?Z zXb1$73K;e!bT};u(-4oI=WdxkB96#Ovu8z-tzy0gcLxswG55Hb0kh#l4u7y3(O`JD zLdB$v-JSit{k7UGN!!`q>#Wz2N#dtdYTDI?HVBd{rDj!V)G!I&8Cvw<<_f>zGG!2~ z>G?80FX$?gIWRaG#cnY;9|(gPI^e5QzFe34*$~zIETru2^@eN?NH4j$s1{_1xer|3 zuBJNMEGzN6-#|qwDfc&AAl9Yd$l=*q$|^W?KA(L3-D9EcEmP}Gf)>i~xx2jJ-GapJ zIY!*l(_z~fTX*mNY2PkvwI683v)IyzfJ9Q)A+eZmJ$*F}vezTA_kWKhcF2RX`3`{e zUHJdn^^fTcrNAsJ^KcFyGj5aeTT-`fGRzb2Fem8Dy$DbDST&to=};ppS{kV(TbI|x zzgr`2qnEmFEVA_34vdz>};$3mo(d$zkbf!+k{# z;=jwqOD*4O^+JY(?OH!~%W9NWx>(ba4` z8!RXPUCaPCb(>LFo*6AgHXC)xvCXI(P1vTqR*V+%J3APB^S7CdziX~89nyQ7O^C_0 zlJdJYCFVfAmJ}~n9Ad`mQN9u+*ZL{FC%3vA!_`es1!4)Ky9Rm+vBSpVRV6MPi;H z*Mj8J!|aH|P=$t}-xYI&72OUpp`893G+`sqgt!0~PdsTM>WnpA6oDbhPi161#I6Z8 zlvHGfK4^SdUWL{2p6yp}6wPTpFzzZe+$6RwMQd-CnMT+3V7iHtP6n5_zdZVEZlG<2 zGOmR2U4!2nx?@sZ+d@Ny&X~Hi`4*&4O>hqwFr)E2#mD>cLI!FHBbR z{?ku^YR6{%J}3mK-L=#sm+KfZ#fJR0hs=ba1x#SFgR#8S&y`#l8t9^=Oo^I9GvMHX z?G(gj8&O8p(EALesBgGxV2ia9w(Bri67Z0ul#fbzlNduPGgV=XQ<$ifs*39nK*C0O zZn`K zZ=fgF4kicX>zXpe=`(1hY~{E^;WWyC`xSl5nQlk<;zl&5?@iKAdjqVwm^J?|g*G=U zM=yT2U~?Z7wj7S69N`}xwp_f1v9TM&%VFZ};pHf`B)cZYTua687-R0%wK3+fy&GZ7 zIYwy_AKY@MbO<%jnCI?F_`K63d}{gBbbw@|4T902D3ODFHk({^>5L(_lD^-H3I9ps zK}qGbobkjbsVU|DK^B4o%kLDE<7`T(bTueR@&;;Vq&IOP_GAv?@bmfSCdWyN-I9IR z^CN#Kd$pQ>1#Ro<`+lZLSMgt(T9+O5&XKrY`nYQ1j3m#j|0ouGt0AjPAH;`4>^RgV z>-IWq1i;K-Ldt0-|G*Q6U$#N-n zMatcs7eH2jt{=@0_wRnTfB)mp9^8v+MA69LRdM>FTnwuDxzsU-_si*v-Dh0!KZ?_@ z$zqY0ay8$5I>m7*#%wRb$*7{@B~G}G>=_a=)EeXUcJiBoJZj#u1?fG3hsdNRW<8A_lt_Oj*@$*iPYun6TiK&CK+r{!;Cx<_syU1u-%4pt^4?1d zKQ2KX-{2G_9+e~|y1rEY66{{|J3yd;+=x5|vN^2FC$|}Dqaqo`j*AQV;6=$;j0L56 zm7A6U_Z-&|h*`L}Ys*?_7Jem(i(IfRld4fshN!NVODK_S#1`OiT6*}7OE zL$4*$c|QcJUFZ|w8LpJ%h%lj=hY~wGAn!xiqaZi5-1GDEd2vpiUZd_p@&t|i>Z*Hm zADulv8|>ph_m4viS>U;J_kEWyE(T|l3P=%tI7rAy?hDuO8N<=ORds}C9aPt+CS0%5 zw8?;n&ux@?PuQLZSb8Myky1BAxs}vw;A{in(1aK^S3xXO8eArK?m((Lca(q;OPV)n z*v;cauOcN|uq?TT6#lJtKWs%El|zQmSX>?dHsH`Jm?6S5)+t8We^3%LSex@i1q=u5 z2l8{qZNrhUI#)%F+d-PK$_kdkB48mKE`Lh9dhtJjc9v7L9I@wdgO%5?^Bumj}J z-1VxA8x56kLwlkwCdH@`L+8Kkwpe|)b-RLt(KCKixG`4Sd%)TxPWxnVR!*QF?xN7o z?)S3?a6+}8Pw*@I1P&ZTF=?EIM|Y`&&mxFxs$^4ebV_{V8;2BBauQEH}D) zgJLQy?pUNoN3PUJe6(%dTgj}pYr(YySV691k!Bq^hnY~SB~`tZRI3prn0{sCtuhlE z(bfDot%Gz)r|MOH#ba7wi)4V9xJs>EwlAZBrkut=U?pjLaBQQ))i zrq$q{xX8yowzfC-RBKI7U#T=Did+-h-I!V(P0funnm7H_+-GQAPY@llQ-uQegC?su zqov)y3nJ*ph6++*Z)T6QANLpp-j+3SC-^B~p1H5dB z9?-{;MDq-ENNbc%G2}XT@t=E0eU5Cn3S*G4h-8Bl@Azld-(hH`*&bs-l&SJFQ*#Dy2`C}SfSj4)#r*!rzXD>$ z=A1%6u`_CsXhJ!}^phvzsI z^w!6?j^^3}HF1*i*`y^LHxI|_9g1)mMkTK}-@dBKNu7-;-fSeYsc>V#`kTmB<8und z#$`_M&I{#cxu|)8=&6iAkqz_FXnDDu;5hQxMF(WP93gNsI;7T%VutzQhEQKr%gGq; z2ueG-L?NJWeN<-mReY5hj#4(%HN3rg35nAD-^A#*Cq|R9#zl8@w1-lVK3QcTDLO*c@v(3~toDI2 z4c+=1u8b~bH*^a_sTq<*fc`MO7#Irq{iDd8>xlK!tgEI5I6`JDDSU04ldQl&%bH?G z?lC5BBP@Y)!!mZ-{LZ^-ts!$CNh7*Zv$8G7Tm<(20|pw&_V)%4?(JY4p1tfZPk+S8 z9$F_MW6*Vh;O~{waN(MoqGACzvqWfeL$#R)lJ5mcr92u(U_jr5ZEiP-cjNiU4mL^6 z5JK5~erP@ifi$FfP^BaF9{1c#vZeexI}#!#M2%k&TqQ2bQQ2S#x(cNDz6@R{!;k9R zU_Fv6vulYuFq0owP%tG)J1EB!of@yGi>~A&c1Cuic5D}B>yQPG%+YvnGMG{Q$9(Te z3}xWl77gQ@#<^C_W>sDCHDaiyZA0F4Dy6nN$UC&c*SFqp*g6J|hHks2vdTUM8Z?{a zqoNC2nly*0joVZa5q1>%VxuyXg62GqsMY*!o$XReuqJ=8xw8J3hE!^VD75o3H`1wAsZ_s_o6Zlv&ewppBXGMLR{dw4?uXO4UMROze58+p{?Q8TN(jEkdK^S{WbVc&c~Y2G&1+s7 zYtY3Wjb0?6Bh%P})$}D9?ix(#UnB zl8!GRmXf@XaHs7wvg>G;Ay_AaMa8I@9?NV%COsI53C6tZuQyHCEPR^~$7zvGnc6y0 z+fg7eAWsi3>XBk2Y@23+CcsTIO*fDTtd`_8BqAG&zNC7>|A9)M;kf=VeJ0VfA8jo9 z0@9+uc7CbYkjQScua%7%bFJ0l5kawu5`|Kdci?cEFBH0iR=6O=cu24h*d9VnwGAkn zK^_W1fbNQ{2nrNuM$cYdORTZUnEigNC|+F@a|6&F;Cyd^)E)9aRA`g^SuHeKVN_A# zMUhiAG@B}TO`u{wopyZzO@K`{#{NY_vSe`0uVUz6SIxI6DQ+9MVi4`O&k`_+)=jSz zseA9h{QSaJWIm)|DFa!qOW6RXauqXZ~By97Ei9(brHp-&>W=(~tr!#(`x_HFz(in}KP+2)0*>pDb|$W4kq zzLEZ5Bl(HA6krrLMN#8Dww^^npb;vzORC1cs<6>0utSb=YY@}?4Ohyc3T^BWr$Sd& zgH~dYtR)n=mXL~j@0bVfV1UNh2n@_$!zkO7&*7YVU6NhTL7GrE6r9|?*)2$Y;X$H{ z(H$r#NHsWYc%*Q+irlOa3&(cTNNm?!l0YladaEUl1|{1Us7vpIz&2KG5tLRR!5fU6 z9D;WIRxN)GLT^ldSg(NEcGiOL?#sjr*whkwKT~7iYM8SEgYRxNiz-A zQr}_~+3cHdGS)dD_BZ0*y=WjFZnyUcAU4{03ITjHW!a!dZ}YsX_OUHE51MU{lc8@o z+%Ijl`HBbG;k)Dyy1kUgu#v zpynI~lNd~3nmADQE|P)!wByMqX)Pcz=MuR;aIZ9?_CB88^4)1B!8Z}XRx&-}{v7Qc zhc`(n9nNffl?<2vpLwq`5v`rP0MRYD@fe$U9t4=8woAk5rkrT`T1LzFdmX1G zN$H$9x+`vOM@RRM;lDJSg=fsc#%1*BR?$`xCpt(hS1D2zRXkoddvNhp%Mtk-XXpfY zGqWd#9vBpxC<-Oy&l@~wq7zY9%p)oGp&KH}?v5fs=T9D9oc3)_E$(c-khtIm|7Zl) zdSWsxn7*RSGMVMFw&50W)0=~h{B|e6z>Y;n&Z?olGYJs=Fp=xBenww~D9o2AkUGn2 zlCzZ495!`bo(=)W0B7nQWELl z(ACKK@5(qKyDZVS^KeYiIXhR)TXZV5qWk*=q7KCQ0y#A`bcIRT*waQm`D-k%ylNmdX}ZbO1-qBQ1o$uT1*dC2i6x&7ib zL67?kO>Up|vNz0l_w?((9bX@_Rjrdq*|%lk#XYTilxE-Qb>bec*;a7zEwm+6sA*W+ z6z9IvZDINMVWUlZ3GL6%oO?#WJM9n9Yg_*y&{a{2gN;9?e)2JdiC%In07hm181oo9+LLZNQXV$G>)k& zMinG9048iHMU!c~Y{ zUtU`h_V1(d+9I#`NQ@<3Ase&pX^FYpj>`Qe4Ws0n3%UZMkC1Q#1Z|}xObP(LH!Tv| znPMgHD5pNBCWJlML(9vc^>XY}3hQ$A&aZ$W7!35N zyj)%yP?)Jd=Gp=ib3rSk6LWcr=Om&sM5HXHY^LN?kYalZi{$jmkQXVS&gS{_g<2xq zxNnT`+1Ik5-;U&L=jHMnIkzMZ(qMu7-psXdzdB&a6L!$wVjhu4g7vh>m%Y~FKe3fOEM2qX+-Qm{v|E+H`>8VOLHU4u^2Ls9MdykI3T-U)-`GHgQkgGl$#(j_|QvlWccuwhnL*-xJ{vy;E9N)C=uPpVgN9^W39)EC%Q zx3kNN12mgJ@2_6H8eCP&#qyLx%fCY5&7+rx|NHSjpU(c{@1OtQ|NG+O#o_*Emv{eI zyEACzkz*wh^gJrcNtZgl!`G!wbpFkqdcon!gZQ!XXL2<4%gRNvx({=FB|bK|1GJ%t`a+#)483e&1CX0iLEku zRyZc;PqA7s|8~0Tm1^r1Y(@k%aIri)n|PBa8I``wL~bz+-KFZ!Werof^M9y7DIs7C zis|50`J$W^<1#1C0DtY;Y$uxSonMWDiq@UXZaCH+&XO{LgVeHMgPDh}LokNfKy1MN4U@*EW)!z4(n=#p!-abNZ(kc{D)tEzuiXD@Uj707G9;Ya$h9 z9XRsGKUUm)L`K0V2QZR|IgAB&U>xT3>z@C$a|ZWXd<8_Zb5<=m!F?xue{;~@S}ooe z?aMfhYA~XDY-Z0lTZjnuW?*}8gg+GSH5mu=-8YI5cG?@&r&h0(g|*qBt`Lo_IEMD( zQ|PiZhY)gtZ~^i!F3R&2GOeaaD=9zJfux1J%E*=Edfgk2Jv7GkIidioJa=)(PkVmu#ir zDb~@LLB6F+t7Af4?rZpr#}vc^7;C6?g7GEG=-dW46&K=ge0=Da;`sombF1KlUro%?HXr9DIWXz zw~@EwbUyFaw9+H9B{$YOc);Gqg9q0S7JEb0Y!(-G1uO{FC=8!2#5L;Bx3_)Q-Tqc` ziMlAJ(g=D7UHy4^j_$#Z3b@vZ4W4$8Oz?CFj1iu6)-Z&623YN+6ykJ+u22SNi8Y-G zm8>mIHsyh3_1>$3)F?8y)R1b=-IU;ekFr}_)vv$O6dmC|18Wt0>(DF9gRBpo>mcUo z8@($W+a{$w!KrjQzg_BB5&Q12VjP~4H%G2RcGpK>C0wHq)-ajzh+4=#^i5(D7~^E4UU6giMJ+3!QNJ1FOFMdt2^WjK-us}zTQI0MU-PUJ z1Bn-TSux9k>Y57nMm?*TQJ^Y|+QN{^hA42|0k<>G7wB)joR5my-I$;hxI>jw3elr{ zC0V4CHiBx@qPe_3qx%IWnU`e3Zb3F+goh`Cf0Z-2n!u9o4GC;xG0&$p29sBlcgW4< z5sG77UX~M#>yy_Pj#2u+Z=)1{eM8h}7?N&lJVu1GAjw}pdq9^$No7l?OzUwPPR_<0*ZF96rvkx7@^gB~&H>#vBDFD(9?1Thb z6VH*V)_~vEerSl1Z}{#MyykQ2rcWBI%p5dO5x!}`BjY9<9vvTtS;DxdZq2LG*)95J z@8u|F_)lt!Q-efAXBDi3l zjV&5j%|-zt5T__VThI;KsmA19O$H(5r7o`Rww7MwTP)qCk)a0F!$u9x#&uaQQ1c_L zVd0CMXVfXx7Pblyu&7XCKs+B0MPpa`d@(Gy89iq**t*}CIfE53|Gruj9A1b6;LZncvE^zI38Ae_qHn&G!mo#4+wUSCZ|&+?ZAM%|V`x3CRuweHexZ|_5sf+R60 zJ*$BC@5>^<8v!*SyT~zcF8niYZfxleH`4CsDeRp6#(u_B-PEaILCg(1xU&uy284(TJFu!@uVBAKhRAtquo9+qH{*oaG0*%2N} zd^|46(ruCrF^4zzA)W|W`&w4CN*ybziA9FqJoVfBUx=VaK(S(kR8#zuQ%!q%r4myv zZ{fkJ7aDZf2_kdXH*7I6i)`3LK|=7WcIgIpzKd(3r(be+;wWVzkMr%<_MH}!3enaV z)iuO?I91$hDc^LuE#Bw+oH*KZ&(E6aJH@N=9F6agh{CEYJuTXRszj6AN>E_mp5}Eq zqKPa2BqIv1sj zU1B!4G7a!K!=zz9Cb*8nZpb8CMSI}>=H5~-;Pt8wZ{FoBsew1+D*fl?^|kuz^jiW^ zPyyRdSU+q&zPiI7u4uB1$n725O zC+WTdge>O4zTd7GSqzND>ZH821$V|AL!chra6`VLU?zTNJwC6wv(&O)+ZRY+fxYR1(N`@h`kEIh+JMdoZh;1!~k~c?m}hslnjrUd)#xjP_n5 z%ZO_hm7l;8mMrmFyGZU0?hfv{l!3f5;^CxSwB+kR0B&wjxV4y!{sw7gXHF6QtE&Rbbg%Nmq z29so2l$v-rnBAA<7`aph<#JoxgepN+N9yC!)WDKjeuGS%@^W&D|7f^2;pIqN1MBvD zYB*aRqXr+RVOz|e2L{alP(JKCJ1*5UU>CXKwL%vipw&0I)8%4OO@AtjSB6FDOmg5v z$@#t-=k3ocjo4ap#@zCnLf&lheA|fGf}_$Uq_OyyJi2U#dUlHaM|Do&y}j0`Z%z*<*?j?I?pO}_)&@KQ^co00YEXotozy$A>FQaIM2I1jOTJre<+m8 z1-h65Q64WxBuwa@ci~m9_bx8gh^GXP`z(@CD(Nz*iF{Qzm7VPuR4Fr{^r6@AX~^3rC;aUq>g06HS?`{(4#&Lm+$4DHVF}srm2@YYA6VJ!R zAzKbFls+uxc{w4Duo#~^sv%v#mQ~BeAbYkvtqU{^T#zKOb}&nrSR3FslbA1hC`4g( z1GGJEBV7{T1<^=~;IyeNPL|l~NO&L2B9$=7V;@atH0Y)$Wi9B}PFY7&BP^Dk0Ui3y zNm_FnR?{>TSbdwLGcwT?&5o8!qvZrz%pXxnych9iub@-aE9S^xYRiA2;?0*QslyOI zT0F~TaHRWwye!?p?HVh){BOB9n-@G#Psg!XfM%+&f&rH_V?Ru5HFc}e7A?#W8mu_H z%&BR{2Bfj9pStC7XqF1x|8AL11SBt_y;gpNPmXwwU?I(a$07`aRtLX}L4st&TpUI`Hh=KQVDD<4$lfj0wI zvMI5BkL0IGJhr7ho{n5gurDV>z#5_!Xd_>?p8ah**dzmxxt3WIV@UoxD z53eVr4tD7qVg;c}zB)gL=|gffkNKJ4Th@I%o53ciWx1f^1`pk!m!d{kktanl{aGn} zG|Sp-rr=WC6u!7%h`LWu(IERS-VNUrXXtS6RoSUs!E6WJAOsQz+lqYT=moGFHPZyA zwMp%i4|S8Awc0#>NNXFHE3`D6c#($C7a06bWMZx@a99&wh|T^NLn?p-rL3||tzE5k z<5jzVqpNm*Em}dyL&E~8fJLYyPLP)xd$YjXYVxv}dl%+}2epD$gihd$z5b$LQ{8$| z&2`6kap@h@GNR?tR6MwJ(S)jLX{gmse?_|Kocxv?ECmnCzjUWf zEh8FP+|3@r--kYTcS_j1h}0Xrx*MC5gm84Gy1sUO9stn^`oG$Ct!4uy@URxdVtsmS zvHpNAc%1w_1GGT30Hy!o)%33{AHR}->DV9%(d<~aN8C=1RP5e9RG5B=YP`w}rPs>7 zZDpZ_C$r~aU~#~)ow`=GUM55N7U9Ia-&^GSFtZZqBUpK~=qtLJ(J()rv4d7Aq3-EU zuZg=WUcvce^71WoawYQYG1*EDn(M#POIy=I-`vvjsduzlvkCBe0qGMaQ1+5wZV*&9 z3PJqRPv1g9-LfQxUK1x|nJDN&nV(J&-y^ciW4ojCe14jrqimLWR&8@h^p(tyu*_GI5=~;x<^5NNJD-SRQWzE#ike*Gt`f5D@r|`IfR^7QXtrmCS z>_dMMV>qUUNbheChNcWxf$`aIVe!<+dtJ%08p!msNXBd}XWE3#7korYW4Nr)uIHj$ zB^yqNhPA9M8P4_?I+5ge^=TOs8iz&}$VLoh;m<9`5`&3t$G1TgjUcEc$)KdFgh;~y ziJBr0vwb*Yi6YTxc)>Ir+7x~+L9Be?>za2{bVdz}`z8?WNcHVZbhs!legS^5!r&e# zV@7}E0(h0Hv^++9-_a3hvC!K~zzM7Q2O%mzyZC^~#!JVxhqC9sr-TzBeQ?TD#&}wo zdKF|RrKl+ZcIM!mTysBs0k$^v`a&cv!Si62WNtv|nxnOv!uj+_n)Fw9IUB=J{8dz+ zB=A`wWv)kaIEy=dUs{S+cZIi0cCI5?HjWuE@n7?@BdpMmlq=xsWeH*mL&t(lID+3m z$5WzN%U7%sux=!Ao)UhH)@X_7yg-;U_(%(8C+lL;9n(_JKW!2;B604>4V}19=nXYxrsoJwq9XC#^N!%7sYM zMMj4?E!tke&XENbmXG|OkhL@u*lfoQlCO3YH>|Z)-XKk)9QqtnmkmhMW(4V?m`tjb z%6v?aVzD&m$p9PKrJu%~#jr?s{6gxmXpfDZF^Mo>YynNuYG?G^vlYb_&svT&4+_!j zO-e}Z6RZ^(P~9IJDe&3tF9<9c+yC1A)?l65oL5}+(x^6u$nLmz=U7%vft8V%W}^#O zlp~BTnGOGd<}P30KVRZMx7F;WLr{^!nk@NrW0wvY%epT9=a1ign-Pdac|9kyzb}AP zVdikps1+f%L9o=BcdD|Y9_6#98I0z!+l7;kCG#pNH>d1&zQgtQExJf%f5^Tj@5pe0 z-nJ>AoC8jEySX~0NUl3tm(m-B%6*h|Iw+-**RW?gy#d^FC;6y=gXZs3>BxUBE`g-; z4}AU_rZXX@raP}|$J`7^NPi8@ECVQL^e=muJzY4dt~kA;w&2@p=bOB~K(d~z*f>Jo zxsf#x5ZTAmt1uczTlfw+j#kq~ASx)yjD2Ecp6Tl$(mw@A>B6gU6`-J|IpZ#U5WCD8 ztTpEfPC!3bb&CzKJJr}))E=&|V_{R5QCZYtW1gfY0ByF>e?ql?;@Rc+GZ z)lpPlm(#90Q97`?%LPgGtQl^wkM3A1E4v$M+v7!duQym!f5#E{IwxbVPc;GE;=B9! z{af@J;qM1po|^FXLK|M+ny&axM<*810_!hy(iOkzp3NNPV%pMNLp&Hfyt;zav(zmU z{cetBZ(vROO=Viapia8M>(LL{{fgY%{YzH;;@$oAg~e^7T$ zI$nTF^qC}|FUx9K3r)nWuVgTXr?Y~Cl3MjbgJ9eQRanELw$#~fIf1E(-<(#!ten9v zU360yU;A0+jc?C`6I{%bF-};Zo#IPdbDT*PRfl?(q0q0Zq4oS=&8`1LVQI8QT%1wf zs@#WM*cP=eC~gg57fuA+MN4!szql+-$<-?LCvP~&c}413XdE&P?EbCk?X->ySSdJr z<9AH`$r=H@&KA*~M|m;V%O7o*DJ0q=lsIh?mP=HK<4xi+CcIKd7e!HXRKxl5a#k#A zMK@1+}$;fV&iH=q)s81u0Q^E|Nj1;cJ}YzrC_@(*a#0guiAD(q9|&;Kee=D$H9&_J`lo$ zJ!)j=qM*8s9fJdSG_Q6R1uUxEcy+PR;{tZ}ggm)U%LPW2LL;m45<&6mK11rxU+M;W zbph||Sx)E-$fvR2&E!fr_&WRNlAsz;|5ajSTPS8$O-NBWs>VfvC7+5h^2=gU%{aW% zvYd<&QA(N&Gq=QzjJ7BYWcByIeOpVE!n#!Z?QwCsJU=IVeG?)BBllo*tOxUI^rEIY zE~dMSB^sOn1=^L0VHm3lescMj`K;dkcu|dZ_&0DijfU&3L3EFFsemqj4fl#ml+rF> zH4_7T3Zqk^N`h6k9PUGolx$Sb6uyCaO}=Ruorr*DgCRgvs0vRPvORY7GmsYKJFQ+L z@@VdYY}p86+DqUmv3)cSaLP|#?E04ON-0qK6Ie_%Y&XYpD+OIZg+x8IYxJfO1BVDS zVV7ZAdJqbz<}9;nwwy>1%5)h*oK4C_`NX9$Ztn5Mbkm zQlwB(ILHVmNzTV|5!_jWZDCuUL|V`YF|GwVaQDleF8O9(qv?i-}<8(Yg#d zpSsbF&KIZ9?;Roc*zwaZJJc_FgG2vCJ(`!Z#h1z8YY&7qgybqWlvzeehyU%}l{!4f8>7(BBH_zYfo+p_Z-(7CyFTe1beDmg3 zCqNeYZi#le7p>jX@3&g#WT;o3q0k;$MALeSVt+a&cf1Q(Sx-9)cao@=;kEZXF3y2& z{629Yvg)`=*h5TXdZ&dB_}mNn?6x%KG0Y@3=Hvu6>d6UO$aOmS#O6=DTO4bN2|hgx z1oho6WW^v0rnS9ZEJlPhUaBE{5frB4_twGZUJL_9y_`;iq7f*dej$xhDx4YO-J1sVp#uNmDBF%72KOT zKi3OjAv-;Id~S#GFVB9&_3hqe*=^K!-RAK!Q;lzbgu|mJlZ8T7m+Q@eJa&=_S1kh% zpFRzJU(d!ATm6^nVv3i{@*Lq87LXb4YIsuNvdv?b8sQgyh8z@;3^?cW-JkQ9Ia9Em zm>J#GoH&l(=F54WeN(*v?yP#5LuKRYM;N6Q3_EQ6aRqGqkCaBwcZ5OFXSDDXX$SEh zxB@#4fe#&go-k=tXFFlrLp3umB~I@G#kk(-o4Xp?E>o5kvO`OAR3$?Z4uQ z*7%~w*+bMBN(qQ|)(PjuP;H!PnMNPzrfnG{x@Z=ge3Gi7N`jxaQkCj%k!ylcPLr@@ zlut2EILTEhq%AReq9dJ9AcBd5)x~ngMRVVgASJ7(0tMMb|rB|q|X>!^5oIxiM%UE`(v{;pV7cTs>l{<*Wa#79Hp0FNO zJjvzM(TZE<*bMYmdXVCt{K=fah#S+1oOt~*3#_AfmOErz-o?G+iUrnBAyI_M0bX#5 zC@e_}EMF$p=SsO1wk}j9MFj1eY6(exa+Bt$nN!gZc103EJTTsu zFbD3&jq`omkn8x$ntAG$*+n49BQ)geLa*>Oql1hzPV~*$dlDF5Bs6ddA%`nfM4!Q|+vL!2ia7;xM zN{!?8AG|$g3O2b!@!2PIk>n_-w%ne@4{qLvf_LFc$#w;!Z-06-WH?{}K zZ~^BaL;-_kk7}W*RI7#S`EZ?{9yB`axiV*IW&%zq7AwoeyT$-R;wh;B&aQ2C>bTik z9bQ7&M&1f_fU6RA;!B}v1M%Fc=T0dCHSPHqPcUo8tqFP!n zd|Nr+^bIdU4sEBpz$2g2ezjbOchP=`&^-0TQhe=o)1fO0VqQh9g4s`q7zyU~ST0Bf z=N4qWl)#w3lZAi+GEgTdIr{W6RuY7|*h})%9kLHXP(bd~wV_7|Xx!kRBJ<+owk>?@ z;=mPbarc$H4P0a&Np2(DXQ-aXO@X5BJvO% z)O8zr{dx3@MkFet$U~=e7J89I9cVIW0re5&q%lw(*kqR;Bs^U&v-txCVQ-BN;%}{> z51n#j;5ma$FC%=|&Pj{D#;|(|W+%~$lNceF$i6cNzJ3<(y7gcr&++SlZ2=~oQAYTp zK5Q>ZF0db;NRTS~W<>8~F7jUl`q_%40P`eyuz+S?nij>|z5^m_bNm%+bH=G$wd-N= zXE{1_ZRH3~75Zv}SqnU(>dgO1FH&nO-k0Q*WVSpReIjo#G?ODnHi^6uQO1fg$r)lg z+8E5Hj!J(>B4jaNeh-C7P5(2bBk9gltVn>|8zRe`XAqmteeALJe49b=n z#G?~|{Ds4@%z&c3&-xI51XUipTKT@?^X*8Od(?%U3!2mRCaG=cnlt#i&TP3Z#Wj5k zNVHRe#bydid+NMwV0a+jtQk_;mu^*98##Pr=117Ja1e{P9XWxLtJ@ahwPbM)Z&0?< zkR&lswdd4UF)aY6SPYVP;UL2`OvNoUi2BK&GF00otXy~^6!7c&;*7jYU1e2?HL+78lASNw`H*n46~v7I>A zgd2@&zwADbA&Gm*lUmNDVF zcoaJ^Nco5k!%d84leUQ1CggR|<;Qp9qe#YLP{O-VdG+^u2pX>*Ke!x$h=leybGyqK zIJU_c5?l;dWPTZI=)9eIocaI~yabvp!r>t6UXm_B&1PuZbZHDrm_~)K{+#76M_2bS z-AWgoNNz62x2~E!)5?-@)Ivh=2r^QgqUEYZiOmWG{ZCR+x$-92h9yo54X;4trYpED z@a9Ni0Gacp46Bb0gI*xBFF;e1z%(oc%(JXCG2-$bkCXcVq*)**+-D$enV9t{eQ}(& z#CC5!c9i1iJQGNKXHV8RM}-iyAG9@tb!__Udmumoo4Ni))3a(Gp3F&PJJiNr^g5E) zDF1=;qS{V{&e9FKY6&7Iis1|mB&jqUp*turwEFsmq@YNvzbozl{T=ps;K1Lx#fKzn z6i?h7yXmFVYJFr}K^Zy=;11z=NUUx!CF8x-8(EshM|LgL_RveAJglFc_1^vxyAbEx zve&=Wzt-Q~?olJ_JkY2LkE4tPjpJ`#VCDhf+hvvY0ZLgRuVj3sSm&G(xt;It3tZ+1 z3}D`-;b1&kBp-^1`Sc__+8+#vLW+mIW3rN179V)153X(UmgFPe%i+JT_RNfATPxcZ|4=Xk5`8cQlN1sUf zD4#+2-FinExd46#sP{{7YM5{n$}e{CzcsJ|fT&9aBr_FM#t zgwU6~ASG}3&io~FBo+|z8d>Z;j#c>L=T+`6a=-B1K#kP}|C`xfT+Bb+!ye^NT$m~f zA=h}fpn`K2O*oAyAg2^6g*b>g>7*fXaW*osVEE~u%vfmkHKh20tGj;4=?~M-pZAba zp`1@>Fahb=V3-?Fp5e2pT}btht^2{Ok67k{I2$s{oc9_YxZt$+Z~o3%HE^u{U85X!~J?cMYd0t!u4Yu zSqDSNyIw;g;kt9glwuQa&9GDjq5l04z725Iw2k-F%VGs+TVK`S3|GLLtYbpC)#xNR+?HP&&kM{LtpJPX&iu z@iZs;cfUjc!Y~VK{=YL3e18@!X{Sm~W_+{i>%v<3GU{`T%n?O!Gu<1ePX$FEPxlR<86Bt5HZft}AV`yr8L~ihGvC7{S|77OF zR#fo;6`a=f7zM}4;m87JMwWFkJXyXetz>{VOBYzHo^oC|4!bld=8t>Btn^si&WOpK z-Rc-6(OETrPFfn6yENgh%atu3R5uDWfB5XHnMZXG+f}yVrX~+xOYpB4t(PlNxQuyt z2zU@KN`SmOM>$L{aShb<)V|&30xC&Q{}y!uEL(wN6rbFA-=DZ3f9*mvqaOtv=;alzt{h0 z$$j{Dl*WV(Q5vlCq-JyLH0Kp&5mfdXt!zpIP=_uc@|s zCj8$ELe&86;s2mVY~eO3LZjcExtNydH1w~pR5weM_g|FKfi`L*-Q_g-$}jclXIMgE zVJHaEtRVRzIRr{Z3T8+xN9*O#`!VdnhiOBH5XWh#$<#gSj0OSR;@x}Bz|K~hdg@lu z;#aZh8<6d7B0xhF4AP$>y3C@uz$rDsLaT5&Tswg`Y}03bv;lk75zsb!f`f^H$e@8{#`!M8YYXWOA*bKKWfL_q9Qk;b zDNf>41>TrmgxiY!N1}c_RTOBsDCjTDqtj=y^LU2MM`<@0O=w~`A%f8(Y=+@=c~?=% zqtt9>ka?d4Z0a~?RlEqv&#e271$(xCE9``K`|u|;ru9t4*jA5moW3jU1w6ENRkqXR zav3Y0+|*i)B=T$uoF%@Kms=TR;+mW?f~t60nUnVrni%2YadlE!Rts8hQjh6jERgF| zc&RqtZc$PkD(?Ev=q7My=*A%9TInRFjhV@DSp=!-psG;c0vQI`McTQ3 zM)(Q%KS{XU`^pvi67C;|Kj!wX{|gDXhws~CsiUd?Q8P04-zM>_q$*WH zy1fEpQG0KF5nghyk5%^0)tHRwy=4B4(h$VY3n%GB8`+e#@tjvs%I4y<6&p^RM2T{j zKFSzzT+{5oC*^iofhDr%qsadj&&DVq#%#N_#iCMWy%E3j$Me&{eR0RBz?79dLSs16 z5H8K~ArSI6VP09~6BGNKTPzb_jhDa*78|5|J)a{7DZXBS3c#TV)*n&LY*WcLi`fe;(kX1~ z%3u)X4#e57y|E;egaycA_zNw8(ALWb-5c)7*gU!=1DRrSKQK31nQMhgnxT5)70WDnHH5UUwcqU;82tmt44l&+OFqM~z3^kmR%j8?*JnRp{(Yn%- z>C$yV52n&n8yR_4iOrgxRIz^8Nay!yqBPkieE&8J-OVC`IExCa$!5P{KaZ0{i9S$t zf%lLDFy}MioCH4!)IN}%V6i7$wIF@fNUb!6E%J~B zRY%Sx*2{-dVM%Yw$@J$kRQTgoiRJacfP^-cB>?QU7y1jfcr3H)>A%>+Y7=c9ss0b! z@;ZE#qojhH5KgW!sz+=JBAi>ZJAo4UId-Tx9a+CwLuMsgTmWCCR(hsm9|8DUg)W${ z>9)3Xj!?=YkGysViepolkW*lNJRu@E6mQvnv_$(ZZ`tMm;CUZ9$d~Q_JrBt7@zy_v z?T~8*)f><1o53m=0m0OUYWIuN3HFpVAl4geOBaM|OB+j9+Ag++hMbTuRJ-vKpkM9y zV_OG_e(?8utB*ZFkO7B-FZMVNcNQTxkZ%KPt2_C28B>EV*LDr?!-E|1kXryxCIY*D ze|)?8I*6IuKimnySR)=bhi4{Dy&YKlg5X8R-~8}Lb5HU-9-65oK)<=KH_zAoNYWUi zFw2MyuG&V-__ZO^hTtY*hKy?ql-W$lO9r21TR+~5eO(5x-juyB&)#o`Pfy-{A59UA z6~q5s8$NT!3I_#CCaCtG`11D#l{nBh6Za1T-{2F1@S@^E6;LobKUgCT+#03OzETVmWe*(R~|68S{4?jFKHh-ht^WW zb+=Wb?8B9tn};8MV4Q7W4NLqY5ORQ&!fAf0TL1wRDjLqiP7ecMZFTO-YXo;LVCsuz zG%KL}oc=q=YP14Wok&p#Qp`(xX<_5htoL|2l*p#z!=(fNK%2$!GGIvz@0oLO&c5k0 z_bLw0XM)}{%Qk}whCVzJZZ#!QVd7|S4S^aO#eI^1>yUSB@;Vx(V_vM}+NHMU#(pE>o7yhab%nc@m-D2cdCDfWab>3Dz z>`z=7P1oJ|cr1MlU*TMf%ow-E4dMKm&JGfWQ_aU+h-Dhjs+Y!Az)zDRV#_7sGP;@Z zG{U*?f3BL2a}0miY;vRT=aW}8wX=M4=_1#>iKXpPLos||_q-vzsB`=Y=ymzi4{|hp zNKOz&YN0)49cow&9+s0=wlH%<3R4g(vtL+gO|*IYL#`RK4)gR{gzlXLr93*vg!vgx zxSLK`f{L|M`qoDc6=+=wHf+aK1*@j z{Z*n?n@_G==8_$!u8+KI9%acwq_AHbZnEi)BMchokd*K`FmI~1kmUvt3*IRam;8MC zhPEYW%9?$8XdcK;C-cB_Zg2S!LBOy5R@+vewR=`?;I4bVCeD*uK8JcbU&1j-5(f%= z1;+N$AT0JOu_ulQ{k8tYjp)b{hMCSBx^O4H((aNQlJEG*GhK!zb=@s&NsoZf=-NN;w!u<+xKH;ltY|1AS4U8WJ6O3oA|KByijVp(C zA3u0Hx`F61w#b%1q$v|{6FMTHS*db8p&JWKGc91kvt$lZwrUYV(s-mUn7iw%2F+kx ztVh#%R99u8J%eY6nt?w&aqcBv>sIX9^kYF={6_L^I7*S^w<(~*9ftbfSR1{&s)f_3 z!yRO>ITrzCqc4jhq)w0!f24YbVu+hCo1OW!GG)NP_(O^ij8kvTQb~8`cNP(;8lT&E zfaC0&%~f?=eTg)Ibw_D&rB0Z}%4{z~jvPi;;h>g*rjd%#x9pb=NBSnFC;Y06<$%4I zR0W#AXiNtm8D<@8%==;+@`w>;?Y*Ifp>9bB3*u!cY)^B3q>Pd|SI9~GCPOImMzMxS z0W*?3XYr~BV~#{yrcP>t-+vx9jbrNQp6xCw@&ui1V~r$rxJ84z{v~Xr7~H_H`EJ@9 zbK-%KOBp!Tq`9bJ_#X)X6*FuTmA$VY=i${E8^+)#m6p9QMKtgOh&M z309&;cHyx@aF)%*W$r!+s$Nua78@%ebPQdInHaO><#iitz)E?UbD5vQiPQ#A&|ZFE zHKDnl7ZX8Tm*=4upGAdr@oMG6Cd3%w zfSOk2J7ivW!`)Cr=OS|QK^-mmw`Fj44$7P%9n}_8!+2-4*ys-Yx(SXk*%hiiqMpAM zGoB)zlFa(*paGP-h^K(ClPa=_cgRw{U_h>dN*O+A=|S9 z$nj3xA`oYs$!3E*R*A=%<$OV`Z}cIrkt#Ql*2klHd}Gv_FJZ)X7x3L8yb%vsT%)Kg zW*8YyX=o_RUrT&_;)f`D0v!ljV48VBl}Yo5h83_@SdcM+tbYXLoL9K#ZZfO1eN)mw zl9QowLi$EXm`)@qg@)UXVNgux9~oenYQid(u^qK1f@LfS2RuEY| z&+nYnclnYTPXd*#r^~TV`WyfLUI(%!G@4?teKx1mIk52Y43fw*^4+mu$6Rig@rIqq zay1y^@r-6C9L}3iT!6gJ+vE%q7mM*Wn5gvKwScQ?DjhMGwa(x48tLPc=Kv21XnI>p} z7k8zBc>|Z^T(Hlu61jYO(8EDZW8(e_eTxNtFfxPr#;zjp^hLg$WCFu;)E*Lq{dV29F zqglPBgrY+0Ap@<)m>k0_er$S%oIF4+@U%B$Br$J!2JE2oc~SR*45eTKvXp5$vPp;| zJ`oUHuJKR5xP}UxLrYG_fA-~8xUVU6w5`(vKlTL-*SPi;ia|$-?f}C6#sns0+~y0| z-M*)QKniRonETC$fBepoQ$NN*rcEa2zp9PAL4I&j)Po@FI9_IXGaEs^xyBkuFp0{^ z*CG=Ou*c)-pg08W!Q!EvlvxC7E75w+WNg<;>R=^asJ&UFI9s&&AFMMiGUwORO8d8% zSaZ=8XQNAl*8=U7l%F12AKLzK3xp8vYFRm90%^?v5_jW%B@0(+Ydv3&$l;Fy63Sb_ zP<)`KX_hJi?1LURL1nU+DEoE1DN4NtOk-|Yeac_pfACo7Tbj;=Rgzjsi{tO$2=?u zj!p-)TJM6bCR>wrveG*=)(00q0#K$91bYo(;m^?@vTn>hJo8jMbt4jBtfwzJQT>TJ zX4-`Sz;{550&_R3<#^_$0DF+Arx-%Bg3}SN$a}Fg1UbetHpY364Gw}C+gQgZv;o1X zis(Cl?!uo~Kx%HXB}|}CY~h=g?5mh4l93vLaU`64(&78WKeA5!h5EIOU(#d_;QX)s zgf3?t{0!`((f}nxti7 zYK)dFJ(?iYLK=euE?%*Uu{d##L|89on7nIhht;nFU&XY~_d#NO%Si-tr}iiC?TgrL z0`;@x)-9HbtzCM)&3bv3;uF>roLPC~ro9$@x3Ui$RiaMmf}J*eVsY@a4eYR5%EeWp zL`jRm&ec`WN^jWEl`WALCI&DzIa*s6U2UX13-toe(qgb1u|C!Nu85YK&t_E>C$G)W zTWS$YZ!%xAgHB~#_7NGF3T~p!e$Lf49<)MDI0o0*8^rqq12;w4fO&;6KD(OYDcWm4 zx1l7-5tHEbTPDMxX6h@PU?%Z%!*wO3rNn;>$4g3<;?PWO8Ur(3Y=)8o0PR>ky1%gi zHDQ6pH}noX;XxJ>`tK z+0#93zde91wqWy9ajR&^nCb^BUv=ag2V1e>$ba-V$dO#L^jyRTQ&4q%rzKuNP{bx3 zW=O$Ub9(q%(G(cmJ6AmG^GqcVLT|55(SHQ8mBk}!YVT8ne2(TYR>pgZicH?HH}JRNE>zgi$k#{4Mh#?HOle;~rF~{y&Zm%prA|VFu)VGH3z2Dz zw!wAFSvshp(heC8l$L3Y;c3Xcak7?eV1)PXQTTXqRoQ)}+Vm@f$ue}D2Y z+b8mIbUK4}{(G8@l}8f;>S>G^A5LE9|ImvF+i^Yy5gDo|G7O_*r0eTF@}u4Dut+7P-XoNyiN#8a5)^@bn1Q2rHMF|ZC=bqMUVYnnCgJ11IVD58cFuf)E zE?1g=_v+ABx!~}WpdeN%qi5+XLYLKoGObL-RC=4|b;+ZnvFWDg&WMLs+ZTCn;h5cM z^Dxi@UFX$#JwRfXAz&%uq$Yg$LG)wVaB7FN(XcvTWjAhi?}x)nRr_u_H`Lh8 zcz0r=nQ-6i>-70V*H5CHJ-75PLgwx1@jK$g@!{e2_^g(FsiP#k`O(fco};~xaIFDk z$OTEmBir2Y*3AB~@6+k!L#=?FCi_rV*CKGe%^AGn*K9JodY0pqdGZ{E*4UZsmaD3o zX#swTN$zsk^Qal0_}0c6IVw!>O;a{X?~lEAQQ_L2bV3=z(Ojlc`3*^#w$2)!4p$2f z{nf60%T)Vi_FpC~k#NBVjq_PTM7l%d49E9-A(87Fuc*tHgK%#u6kyNH{}lQg!q(}% zTM)8%5lR&)8Un^t6LofEs9R)vEk@w}ylV!Jo)r}8oi>)sTgbd7yWI{SjUNw(LDEkq`c;jTcW|X(kkfo2=d$yMLX$W|wK1 z)(WR@tO^qjvdp7_Vx5-m)wXUxxaoeMfmf_cqY^&I^sb;qg- zi?s1$nVUFJ-(76#z_)?f67~1IuMYD?8Ek@?j+me7F{uq;$ zQ=2CG+P4V|^C)JpWAqtc3pI%-vQK(+B}m5uca(G-p|L^diSZAZi~xTUFlF3FwTx9^ z_NkMD6c~IHXY-4*NkIv-HD#@&JgW6?-ZR9z(YdfB62f0dMPfPb-^IBwvdotHzW%s7 zydPMco{sUxh>sHny;qN4q6TaMvNcK9$d#R2<3%K zam`!;X$k~w-iL%Td2V^>wa|`7H6$@n4?#;<3C}_r&2&Qp;3Wuz`=ft2y4-8rK3&md zCtkX*(4THpQ3f*ywcFq)D4OC_6w0ZbUF(gV`eL?bGBaNST01B6rQSs4HxiqJJ#`aGdaE2HsQRO11T^KNP z;Cr;faJ(4q;e~&+Pv4LkB*A_Jtmt1U;!30Z=J%TjVQwJA2kG6u9NQs?Pg%ScW03?g z@DPJFPXm#@2k}7+)jsqau2pw`uwy-3d;#ZN+*alLt7&R#et*>E>-X&{ckHmhqR5G0!>SqiIRHfZ?qP_$A@B8izas6`;gc4)u|1v&8~UNO1Y`!D3R z9FbxZ7tg^o})G6zFtJ&Q-I(Im{xn9(6) zB|v_qwU2MFiJQAtp^IN-tRfr=qoksD0Gl%PThCR~mf%3wP)mf5cguV~_L^ek#BDPW zeYe?NbFe|(G`%t;!&zqm6Sg-7)edg*j%K06wIvk7ekQzp^t^}vv7#8bhUn5}%Maqj zA<|{TLm?>ZqWEbHtqQh!$PRgaQ-I@%#DWIZ*!0sJU_i#$539nwHroPSOx~;D$z?k? zsI=sv@EdhjOh*`r#1_R_+NJ&ZVsNq!3Cy%ECQ6rxE=y0?FB`^@jup9V;p%1}LsnU2 zV+j@y-X$T+JD(ww6~WO;1a1ZLkY=&tl5;kdau59UrX$XjSi)%DwDLpOa-bFR=oCu* z!`i|))eSDOn*5sMgQ&qLAr3It$*6_*Lz<6sOhgGuA{a0Z>j|=xSVLL^+WeEmlbt7! zo?EP+~|8`*nP-Cy`kH=agTP*qxn*=!>Di9RG9jq%c zVzDJ7`@zJzJR8I1J2k%l&D~3%ifB%FCY1eRrp~}zvm~@npIQF$=lXQzt)D5l7~Y83 zs@`&Ub64A|u}Pe?jKHi0_a&seJsDhC@lNIj)ii>-4#mC)(HMZ_ z0&*kDEvIIx6}lg{8-t6R0O^m9OANZJcVn+G{I}LF`vq{?7+6!b<{AAuFSon09S8kG z1Sb}Rrv$>XgGMovJDGD>-a>6RnO$6N)>TM&W;Xe8_@ARIv72DdVl;xN@SL?&j$-Tc zbIZ{lrQG=eOo%Jx!Wd$hb~Yqy;L%45OgoQBsh0s+$dsLb9?B{%)<{7@vMseqT4E#s zqr02BaRN1F3U>ztZy)ICX^L#SsTLkC{$TEKb%yHliIy?6llC%)quHcFVBYSexgA6P}Satalc9D0muMC1opDk@+Hqawu4u z6`ql-Lh+_cOY35FSHEa(mgBEAamb_%nBaYRh3FKmznDGGV$nb&l*v%NbZI0)SJd7Z zUi?Yt1OHOU`8^+~{*8qXDZQvDF4b}(R6B_Olam-AC0ey}{$VV&A1VtW~d-FgcXdhoalY7Dc?03E3p42TSx z)Xf~JuuTs%c%gBhP&wyQqR*cm(d6&=>;@c_LQ^c2o2!qy3C9TAuxPZ)iikLg4(L?0 z-4Y$E&(ahhFK^76?#DfqqYI_nAmp#lf?&S~0Nm>fzPtk!GrZV=Og_2vM=W8iQiAQ= zU+W?s^DXPnKtVsN9?&YJWXuMop-Mm(L(8WSqt~*_wj+g*)|iu`p~g2)Ge#l*b5O+G zd+MdH1{V6d8wZ*T%fMii8M1Xd-(Qenm`lm`o7o#f52_ptV+imB6XmgClNo(O0n&gK zaD;>b^lgF57BwF1?HoY!E;4&zPt#&~IE=78Ov8;Y>;g^w`z-Jg;W zEF(EeXbp_YMRysQx`sfeuMXR>Yp)|=e!Hj^lDNS@ zI|~FC9ene*A4hz3UZ!qO{cg9*FuWorpY+VXIyJ+JSwvkcn)D0zeqnQ!`f3Al5=nTT zl6f}R%{E>bTT*qSwAi$q18KB^8%mxX!nd>-UQBp2m;Yl5&TIIk#AyP<0AdN4>rv{a$*3Nd{Z@cpNec?!DMOA80rCi+qj$V8 zfoR_?e&x+284PRrBL)vc*jwyGhulYVYx`i|!$K@!05XGxiR(u~y8zqQD>O?rL&9yBf( z&G~5*2H^W4Mr;^he(8yjQVNo?hlRcIa~`{poOgz0F??}>c*2i2MIRV@i*U^k_Pq&B^lzXUdaD4XsgrSj98wcBP9RZGN&2m$d` z(sjP@Vf^U6dfRWisp$vkK7!Q+)bfs4ZRb*}0RPlGER4q0_BVO*_S3_Oa@fjymIym) z-u5$$4w8kcw9Zl#6@U@A;2LDd;wdCVA0jE_+{r7F1?khHM^`Q9E67cL7-&hvcQreu z1#xr5-UlXlhP@%Q<7*6KNl`T9n1Y_ZL@c!0H>OI>`jCAL{g(9(;e}d6Y03b>{W`lj zb<2RIF6J%1$Xp2M@#1siZkmRpN(tP!2ZthB;U4o{A-?9n4~eFRik_DUG4m-Z#2)Jd z--0p;k=-nn;#YOQ6L6Qb_SBIWXv5xlywo{z9?@ZNYV??9I;eN{QGK9xMF-kHyyYQO zQ93L6ktIogtp52A6xJKKm=VG&SUBVXCgbLlJtFOLN!ky`+M{Px z*6m#DmsvmQ0Hnc(sYoU2n8PScSqtqtoiFtGx=zUnKuPtfd=$9To8{U3x}V66USs_x zcMTEcF_wL`swG~ysa2}k=Wp?2lKXx-f^?7i{B8^8E@o)<867Kd#c==j8ue(dxT1K8 z9}i7xGmQKbWFN|08vz*%KnT!Q1YHVx!5ZHnN3JmpgW^#bHW^m8sf4zzFnkN7np)dC zDmxe5$6AMl`$XoY8c=*NQ`(S9&qp0Uot$=PO(=EFnF~7n>f=xht}2a->q%r3WHC~r+t`BewHyh4V9-T>6-)f-dBR~Dw56Tr4MN+ppt`w z6dIC%rg%3bux>%A#P~1t&yX$$I)BJtGvelt5|8D;4{R{qg5gM>iYw}v$4?6b`rEk> zA9bX@-(0K$)bfBo%U37*H~PNNv^U)9@41Id9d*9oYx=6$mcf?VL2k!Qf76iD^pyj$ z7<3$SS!O(!DG1^(1FuK{BnLE3fIyjp%n3-FcJ3E`3{V(RNTHGz?EsmERL*T2TGK!|; z+YR>5d$`r5(R{8$LE@?MP}7SrrYozrTKS+#MlACl=KanP3RB-w;R>tDjxx zp*77CllC9a*RhQmj2rNGeGVIWb~s)}+_cC3nobmJHCvG%edbpZ_8Wp%$((U|dw1%yHKXi4iqGjp zL@;Z_N!3pyWhpv z5rQrRDfI_u86bpg4Ln#Q!bD{>@T6sX435GHO7a<=?HRA8Vda}~^rqG*B`8BW9_ETp zU{*^~O?j^#g~@`L!6oqCrY>0cN<>HY#z9sOj%7lO(O@+<@x(vsXADXyqJ;nYY-tcC z+w|Bxh$ZYwTF0eV7mv#;u-t^O;@t1O$@T+YTqj1F4Q~-m`N8 z&dEdbcT+2`w)(5j4;Eqand`ud*4F6n(nlvy!Pcoh=;o^Am2Q|Ba&>XLqv0}yzk*n$7VoXiDF`j__M0U)+7Yz`OvGoS}db6Ew>Om%)AFGx`~vJmiWdLz{vN}MyIu#u%>f%krc|cgUa)moyv2aChnbhQiYxIzzj*t9{5C=3O#sp_ zSmJU{@Egj*yYcW98YAcN>F&^LVpZS%q88Xc^R_hdW7mD8bsM3Agk^r?jH#(a`k_9!&q< z6%R{YCIwZ|#C%uNFk4goXaMo-04SGNi5rZgH#ka-W?t`Poj0{v>SMa5?RE(khL5ZI zSmxc!4F!4l9wmj(7}H3H3kzdB=v))M-Q1{^P!_6}q4$egCm2O*=m&lgN$4(#46*Vk z(*X-pjmg6#q8r%*O}Hwn=64kC(OBt9gB4DKVdXKnU%ld!TcGj5+%sN5Y@Ad&aCDkY z@h4BYyl(tYq?r25?eVSXcRYk?LBl2mdXaU&L=GfGmUoI~p8Ue4ByT(*F08`!a7b8J zSOvsJcdF~WxzgK#MvVxC|EWEJ-wREz8~KVMW*cvkAUt~FJu zB51UqKAcS;VN+**^{8-)OmSW`;%&1&KORT=v&Avku zbz8?~rIPFK&xFf_80rahEe^G;r}%_y8g@{+ObzMiFWk5EXoyPbrzI{yIT5{^Ly@FM`F9+1?`@>~HRs5DD0RkCG+e5!tMq6pip~ zs0*BTG>{vuvG-*R~K;5th`Y!SHcIv6;VG+gd2;vuVrmCIaW zQht0DjG_`GKPt6`i(+q=CjM}@6Ve5vI(bG5Af+~)a?-rMLtbF#B(hQ|W;Xm)9pu4W zA=Cx_GwuiKllfFN0wQA!#JvIN3Wdt*pyu&G;AoM7t*vR;;5?izw%$fI)L9+YrouLX zFPpGrHTTKfi%x%yB$b!+LeO1A5>#<(`Fsqw9}fzKWu>b!pEr%IoFqhIgPDTDy)EOu zritMbxI(_EmnkTt)NoX}+gEBl9+TjM$NaCXaB(IlW}|N;VcV-&0`5_qgwnE%ARuXm zEW?pUI!iYKR|4UdPO|8rk+LClwHPo%yZ*PGk`9&0F8+SnC^0eeT{+Uh(v8xc&(1Lb z%zXgjnU^R!K+t9;)-BB-cml63o6ySDQL(Up8}Y7Rjf$RL6BU0QwJo~a=2aEY+Cn0y z){#)>#N7TQ7T!vOJ+S#iG1srFbgC~9Qj^eqXq*L*Bn$@-fHNpZkcKq8P0jSeg8vRv zdzz;;_`wFKo%QnKdt0)OvHiq8(${8I+bvH-a1FDl;bsuj4v>E7n#~u3HDyM1N-tE9 z$6&L`HecX1r@5SVx({Xv2LGJEQ&o7y<4uU}6%`EjvmpS2UEwIOJMJMgx1nozq2Ez_; zKmPKZdSLmJlak6N?qde?MB^*%Vwn*nb+hV@JY1QYg1v{%`|)wVvig^>0bCyDN)%`; z79<|!mGv#1N;qxQJ02o$X(iQ=ix_-2E$@{nY zmsFPQ`y}kGC>3An0(=XFs;iEegzUV-TV5+1SE=d>U;MGQ3r`e& z7%w>;gYI!l(lAv3Sn72Pr`RD9qzrdo8(qn6RKBX}oeGaud4zU)GhwVnk6*nIvpi#- z(rr#5xGcW*kj<@WmDYK?x=Qg?t`$FCobcO-x&Sa;+8)mbW`~DAErW}OC=m9veGl#Y z3m3+hb)oIJ@0bSj!pn(5EA0K#bO1*TXT$CUj@^fLP*ejkYrFdTLQv&`#<)l)V0Tpm zXoUWHuWH5+=>Ck{a(CU;cb|*x-H;`O1h|8!{yW-Jtfhx429B^>v%B=SJ>Th!)BfPM zwg2GqHnLow5ti899ZC@AoMaj_Gu=G7P+i}ZtPd!K*#6Pz|A7c|*o$^971hJh)t{wh zRQ^n7xtR(6SG`#N+kG@}_U;vNwwuA23WvAerWP1Oq7?H23U2j>vrx;djE8q_m4Xp8e_L&wjAT!>HJrV6>Oy3L*;ob z&FU06^A5JcQfz>GMugvrW_%--_WsobmnwL^3kSCTLS%Z}BCz%;%Pv0)W$vc0AV}-^ zGs;i!L|PV7DeINFmoM&Lyg&=__k~}c_1df=<`?+?Y`AIQQ?AYi1p?B61_C1ae+#%j z#;#WXi+J+W;k*?CEyasrlff`yh9fiC%al~%CJ zO-+L|ku5k@Y$`3?0uPL!GUS=lt~RG{1JGeg+if^nwE`KGpsiMhF(1n&aj$$h;w80h zg>|bPPqEL9!maW}%MusiZR!O5^L0nxEWwi0q=mMN1%P+{Q2{YVadf(nL1%t`{cDdtqr7ZjnZGa>V+^;ag7^ za+!(XNxV%{N$mDPoWF_IO;rx|-dg%4=gwz75F?NB$i>OhD(#MOIxaV_WDeqHfdd24 zI0+gFu7xUTCfIgGtynwMCj0*X0ewJ%zbUu}SlLo8tuA5Y-Wi&MCgd=^ zh2D|TqhCG-$B;pL+BVI+z77YE`(W_ao0JAETwFlD-*DuJTfBW3kH^rCW%IZ7P>{us zL7;PlRTlw#mGK$^k3t0fW81W4ueL|;JX9lov)ayHEMiyr(_My!2y;vEB1yp_sTjKq zaG;{I`Xd#RddZ_n>`#@V1c{)CU#Es*-p#Tb`OBO?K3~D#;TPG%>Se#h}ob z@|r(M_d$Wptyaca^i$$sPa1ThR7czFG@U$&uhIub{&eY|`&j5+sgx+#NAKVAi=s+| z`~YbT*itiFMgmoG+MZ^d`8KhN4w>8KTTqVL%AX^vQh!A=WJ&(yRt?Hk&pzNBp3petc(5Muj{S5s=Gl+PI5aRPR~qC0R38B zU0qdOFZ*A8wewZ+qHdeX)3Phusw?)2%lnhBCttfCf7t)W{jcvo_}Ue@=(^Rx{{DI0 zU2IM#(`K>1aFtf;=3O=I_GeVrm3z{xuGjVXMOO@`qXH@ykLT5g;?L`HGanbv*VVEr zUN!AQSN7?qoEP8K(`wmPvxDNr^Ecz-&p&*}y|p)Xw|_csPWKmO+g0oR@18$-_WkQ; zd#;t@*R|k=;9A(<*%_W~mea0omcw&btaQUa85O_m>=eaixkjKBhXs9_;M2#C#ZW&V z7QcKNJ%kDf;qH1>y*zVoeY=bf2J`t4|pW#Dgm z(X`d_jeCoI6vh7jqPeV!wp-WB^P*hNibegQniXABbQe`oEoWE^+nKC3?ZuFOwyU{Y zh2hDhUe2lyFVE=pxY(tZc1Ppl!RYAzv75`?>SOo1`i>npPVmgtk{TW|@GS5signd( z*2@6J_6YuaJn5>oi-AaE{M6G~-Tvst=(?|HA*PnJ}ADyLZQswgNI3(KSpI9gm(N8mzzbkuBSImkKhcD z(*VAA2{?mq4~lyMa68-u{>vLNL4g5%7~B;HaA9E-c1(Q;@mVM^>6+I#y5UGI>K)Kw zc)P(R-#oENyS+6sxs{i$zx>jDFTS9sk-?S}rM@tg4wFi`I)sMX+x??0HOg^&uT)7G?p+se~a)1}{XJS^RgUM4$wX?FDTb~}1$cJ%UAJ38qo zt&9JaS7zYwfV>m*3J1ry3n`X@^*ilBfo#gW_Nt1oOJo9fKj@c6gO<% zB~lC6yS}LGUiy^Vyl>mDj^6IQog69=VUV-q64&PaQt1^Y^nE zrs?N#Ow&8EOuuH!5=8(1j_vK}?{ml6{rP!kbi$?S6WDIauEzZrAExlyZ^pA`y(j@m z2Tz~wzj(3#AMVe8JfW}OHO>1CY}g~;`O)svr@P}~@b>NOm#;q!j>r1TZZR(C(}PbR zb0uE9=&Snz>TatF(!4=;}Gmy3(OI{rh?NEVB}nu{B1wryX{ z_J*4O`JcPv!O`Bwd&jx?|0m8;zJC4bK$_q(zVHdri#O%z91$e2oBTx~GNFLR%>DgE zwP@kt7b*LbpM+=AlGeI zF1zBix+pK}X6>8kT~t4hhlQHqXX1nw3lsJr(WSv3zjC9HUyca>@<~DO_tmm!7`vWm zc>ucA(&5b7oyTQclt{Ug5wR>D_XOz8RNT*M!Y_nBh_T*}Z{gN>*(qRg(UD{L6vvkp z411<5O^$BEYFuW3vqi^nw>8xO{1x(D#5VP>OYyl2Ggh^zV~6;zlHLYFh%<|b1y}*P z9qRl0osb+^T7t%~OKU2$5r_0-X!>)Qfg8#_32?BFb62WP-Y;p;wi-3;_3x`Zx} zQIgP)4Ex25)&DaFY=ZxNd8RaFJMv$kE}xL3!L0#M>fi7-VgIYI+<(6+9=qYJ9DR1n zSwhP|^2=pWUBdc0no@U#BjBsDZQ&B&V%==cFM#Y|<@UPms>P)E5kA5}dRaSK++HuI zuHdr%Khz>zA~debx^Ua2bO!;}ee&wZrw+T@2|oQ$t{s=zRqOTup9@!Z-nstwigLX! zuVGm9X2Rc#0uEq$3Bu#_qFkPnvfLe+&76KAAivt*VHNu`oR+#hp*CgtV5z@=wSEkJ z%Ez#{Jf_3qh<+VE+zCsDo6eV@AFSiwL4M&wS1o75FpkM;-E<9*lri^Eo}E=wcTPTO z=9`71M+z~+AW&p&<~WRF!5YnuP}k#Vk{U(v#ROnde9dEtS>4r;^I80 z6DZ*L3wRuo%-Y1>@(&K z2$ReiUjE(8ce9$6T@_9CM|^q1e80fmD@Y+FzWlO~4-RDf_BWg$<2iC|a4zf=?FaP7a9dg{wU44 zBL1hqPl`0l&h1So!N1@Xf`+8VggGd80`BPL;|3fDnG;9KYj(S*p&Rd{nrX*u+tFd5o39Q&hYwYe)33 z)&ze;>F~r~mH;1B6TVG6WDosh^jC)Jq0EoI>87!qZ<@XfO1epg6HrivXEGk%Q)e!=R59+V&!0V(lYP6JRnLArA3|Up1@YC_>*9 z6%8UsNFu8S)4+C#|AYw?kKpZQfwMX&P^<`S|D*+*C&Z7TFE&;e1w7mY$0O-fBbY`w zyA}M2Sguy{>!HC^Rx8j_j6Me;_elr@QimE=qLqY3j`&We>!xjyP(%0i6h*^HghwY> zH!q~gWR%h`ROKQ(o?8jc$nErt&A*6gmTa{*pvXj4{=8bE+Q115^J*$WGxIxpI0dBP zT|KY6x}syIyC|JJy{?K4fa&zw8cc$S>|gxF3DI_&{R!H{{q;-Cjy`pJ93Tvk1)M_p z9fsxvNYKsTrX`hZy&|F&%D4#)ZEC^qUZyacw9AP*y}m>SxkP!2vfIz~KlD z27Ag8lBK-|C{YAWb>_++>yd3{h86^ctt%FEYNQJYFmnUC0xoZUU0jsR?;$X1hf13@ zLw!tF4mIkUWQ7zdMlz{Hy9bsK!iUIFQmaU=3xJkCN z420gEE>xJ$mqbvEcV>%Q({Y+p*6uh!ltMPP?Z}z8s)nZI2@G;U&5gnix7eKdN_Z(S zdrF&B>LJQrRPLlu&}JoHmZw z%<46rSM4O!t2=;)+cYqAsD9$=3a+^`;(T46s%BfeH3X+mc)K1&FMi-b{OKC-eB#N00CsJt&cz*|QGri$-)*rob#S`2zC*fCI%POu!O(kA zt`b0wV>ZC-VMDADpnRYNHE=)tVTfx*X*9|y%lnobhgw;yPR39gr56fm{AQ?r?Sh`{!3VAKtL*sm^AGINH$ zTYazudf~_*D};WN*+Ng5+n@f)=17N`aS!oH_KYJeS)!8b;D|R9%p6IKzEKhE@q*!5 z{h^qHcTmJDs;fk6dfih`yk$cyB z+~~%(bppuT(6-)Pqu(z&SE8YI~WGm;Pz5>}X;6iSUpC zFRbl{zs{o-u})v4QmSXl^rD{8jT6au&u2qj6u}e9;v-&+ZqbC>`$QVAy$p^CP_uL# z2k23je2S1iUfqV{6PFP=IMxLUjS4&;Y5`g&d^uJYh4yVy2X@0x0{y~&r9)hkuCDq1 zMr+oL4<5jf9P!v1!;7|gTI{difZ5~@GZqYhwwpx(Iu~GQfmQ$n5MVD2YSxw1}-lN z92$@5uE_5iUnJ zMH~C7p3gyWf`NrLVk~oIc^Do9YNE2Jr1(;+>c(u&d2fIVC1Sq_=!1_)_zabmfGXM< zx7%vcIrW*tDJ%wN!8Q9ZwGqr#AeqEf$f}`YRD*_Z;9l3s<$Vt(O^R>fuAJVHy!SYcgMJUQY%6z);BXWoHUJ$VTdCqsouA(6g>3a^+OnB zy|nJKcIAg2pw`<^vj#Tp?;57{a^$*CVTvRF44B>lr1jE*bjKT{i6rm@wPrxQfFHh; zYa?}T?HK;wofrV=x}1w?<|+{tP;(6TZwK;2ix#*(q_R7ZAML7|)@OX9SG;-o^yNV@ zYo^G33FIrV&P1-*c*rA{?0Sf1$5eq=bzD%ys zR|P&PL(iGJxZ8P+Z~ZAX_Wr7R8u-!TdU zrSZ|68y5^B3cYx!%ba+$sD@DtjGesD;_oR_u|+j@&AwL!ZduCT6t-qc`@umIyU6qhkjNx6Q{E#(@UsHISlnfP)b+yi5vmm-oC)n&EX z?ATgp#+u?O#VKBO(XP?Z=Y4djx(&O8O&sv~TP)}?hw4qFtV2GJr7FS;ZXfq2O}XcM zJ6O;(B2zHedVX21}`iMls342a@qjIuw6Z>v_;;1xiSftiTUsZ4eut~@~RqklmCPD1ZWhq5x zm>80esj27j6@>qR>OR*E5HK$`djMc%xHK_x2I*^q+e&ncF~ge>>!)@If5qdcPmAxL z{Zzbq`IEf}X9_M2N8VRoeyR4cnAJvC;I0oZ8z>v*G3ef$pnL_`znRaLyB*E9T-wUj zhq~>+P5=PmP?Nz9E3ib`PL~AlfTBa|c!b*~nq`Ha6(p7d z*9Z*M#&L`D7YZ?zzXXql6ZR2!Go<@Su}-EIU!m*Wv_*Zd%<41{qX1Xf+^7$R5!pYY z;&g9$M9Tp(zs&#PPCrr*WG!Aqx*bC8CE?b7Gjp6&U>2ziE=K#Z%NleQG^zLW4Ut-kcndpX$7J^%)k-vQ9 z2eoX9dE;C~WpmBCK zW1yKr_NJuIcH@jlZd0@yxAWSD&nB=9zW{SWG&L9(@1fxfBB^Nbu!5U3H(S*X8k$yD zPw}idj5Zl)_MLbFM~ZcxB!{v7g|nr#vzf-%hY?iRzZ=&GdY}XF%AqKM3ZGRKeZWgd zyl~=<3y7A%QyZmy;o2h(<6=0LV6G=-zNK7X-MCmEzn0u5_$#!CXA_vMV}V(& z5pqgm=IHmtj2ahQuFyPlPj-`VsBn2Iz3=@7e0xEDf4MT|IEfXdk$^!!qc5bkyh6ha z9ixHCMk1Wu;2Lcu2koUyFHB({q3>JUt#y0O2_uILm|jzcWE@d>D{5K+n8|eP0hjPK5NOtsCG@h1Cgk?1SKVd z2!CI$N5b(hEJ!1t6}@nddfFGqDQ4O#gxu%}ZvE7IrZ-KuyxY&rw=2{ zp|d@*rv;5QCRx?HU(0Dm%dEgTDl>6#>z*?7F6_o zjG+xU^gbU4&C=e4fQDCQicMBW2|djc*5t#Lc+Gnj1T z(*u6iVA?yKZ_o$Oy9e|NY(W**YO^wp&h+u7STHA()qFF3Zx9jU=fg0=>E}rZ2ULN| zO4@+N$jK(P`)S%Jrrt6a1Buckb+_oY}qt0v0H87$Xz9|F|u-uwZx8$ zJGdqGTuz*pk1}ezdgLZ{99b!S=4!y>8r+otC)YEv?#$ba{@tz*&*0hTEt6Uw2)$bu zf25_`+xstBDdjq})q?lN)i~I?3QXRF%@^mZU$aG0g0p1>z-Zuoz2b8uCHe?XbxMTT zR+%jZbv}EGLFJ?Rc&xh|^2=xe`9$Bwhp>zQ!i(3m)?FK}x`q&H^!d}tNH1=*w$?J+ ztxMk-3*E3L)|!f&?V*vo~^bfa!@ET=-3V~J~0#IbCV#z9RLxgIIEKE(qE?nBErJ~k=y ze|+f?8Av~$C8OSEo`5xuwZ4a2g~NnA{yulQ+GUY1gQ#~MBP2{TjDRnx(PTz?=gYY5 zlc1u$jPVcc9f3X?_F|QwiohahQrgIGXp_FhJnXDqx1D$>08stidke8d*3D{Nmz|G4 z%238u<0M({t7{Ek<8iL(8kDmJC&5N7G8AVMHMZG<$eCcW7w9U4XQ5>bM&rXstxk0Z z|IuuA*KUQ@*BEps7>%UH>2Dy56e_Fb(-*lrITeVZ-OOec1Y>9`4?WP=1V#6(MspTq z!Nt3)Y&~zTn?$pfnS(ssy%yi%0-aSIhT{bO*jzg})_gN{xKP4w55nM0iQh~RhmA@G=0Uyi~k(a+a^q@Bij2n|2VFeN+81n!I0u6wj ztu+~(B$++}9~QJ~bQ}n}p7^k;Wk=|48uL*}W7X}1noWkXjZjjRWLoCN*I*D&hxebh zEa>S7VaG{$O)(rmxbQW&;`3VWf}eEK>%AfZ0Q?PhQN;m>1C@06l-_X_ z_i_#S__tn-kHb(cfbaD#U+hbV3PB@tC_VR`_*d>)N+T0$_zgX18KaDeg?K^1R;<^& z}|Hsh|a)allKoyqgXP%L?G7#utea z-0o)^_G{D!RQxmXy}p1j)%aC*!me4{QUb6FEi(~ zADee zicbb^z7Sv~v{HR=yP&Nh3X?>1nW%me`fE5O>iDsV$X0e7s0t+FuCdsfM7cx8ktItg z?!-xG;6C`eET?5BL8x+9Fg%8beYhvDLEgQkI?1#3U*}ErYO@8h7fxT(U5iBH4+Mkw zPAHM}8!``{K`wAWydJ}PTqGNN%f23KA`+kX(X)Aof>fqwmfrvCisJuy$sBk7WHK?O z#D2OU+`uUF6!i@R9T3i)ogHqsis`RvP@*Kj9|Z7IWP*{D>8OCtO2WTUQpUoFY08)oU_9dPFAcOT~U z>Eyg=&ga#X!e=h_+soyi8;xu7dVl?gfBBbvC+;lY@3-se{%Z`iun&{nzo^Pp>jpG= zCo98QtAp|;&U=0T598wB8_4arI4{?;rI;N-S)}%75FOP0JLmk@c)bYLmXHVA$z9j( zGk@cXIn7dRTv*QI)rE2E)x{v8Mn|HZ-X3b2u)qKOjBO}ENF-#eMLrD78fw`w2p|{$ zYyQld4pP@S!@0E8gXqv z?r0!nrftCb1ghn7wAULz@+mn6zSy`ca-lfrB2sVAQ_Rpo1p_=nY{$B*7GvazVMxK3 z$Xm33Ci6LWIrQ{#@0lV^Hyto*Ry|`s_pjusBZ7ncfPbrz03Xkbt6}(E>1PQvd60 zMzZ)>uWWtRJ#DU*nbpN8Oo(r>GH%^8fRU4A~fBQ2ceh*@TCHE59?;OnQ~q$AB#{&p)$6|&1qKxdrK*IP`D~hD9lOm6WG58c@~W08GA6I z7aF>sg6ca2p=bMM;5?xp?_K5eaQw zR11StD0@!UEXo=zL95%A>+1q87uHhi3RV!b@zu0;+?9JoFI;sYp&X~xg`!l&CBuNU=_nq4sjD4-?BZWXFVP(7M^26K= zN2O#Ziu^{o0^_FAd;GMeB;2k)FLCOKh#ZMt-6ff8cL{;mQpl=JfTk?uEQ6%du56fYWg z0?+IBm6KFUj?ZXlI|RDh7GEqttMB|#gMoHt-gItVwJS*C497bd5v(hsQf{Ad8{S64 zdeZVv0448q-n2fW(9LmB0z$_6zJ1C#!|wBrYiAQYY8JndFSw3IG@p$NpF=T%kkS;P zQ#ce3d)a$_^RIaO_^n`e0I7KNHq{XqB|Tqc`F?lD+(5Tpa!77bFhDd3zPpv?N4ggH zvz~dMSkswg!!VCz`yD!Rw9HT^UHD}@X!EwOrI(_V^Bgq;@oEjXx=LZ~P8VQe<{=ER zni%cFs@IJzM7o5}l#B_1F}A~s0|l#fQ${IzfEs#y$t}Fs|w{_>Aj3RVwpT-0T;<2~6Q`-pWMq zDkS0sYp5vCYpPM%9{3UlZCaDSV=n7#lNNm}cpo3CfC!uO5#lKZ^AT)IP@qwZUX~tP8 z$3g~De01FF7fjwnSC}~?I0j=JVDDZ;g>G`_&GDYwZN-h!fA93_7S^(Y#ca+|zKG(L-QK1)(YxQQz*jnW zv#cbvd$Tr%Xpe)usoBR&vK0`aF~kNI#?N75vyS5KZFSXi;wDKyP&ezW2aH(|K#5+G z7)I7(;xSz4Bm&|u!K79BPpq>SQNHw|ZMVbmLU2D(wahmqci)iv?>EAlaD`04l|Og6JxpCQ+hLkWz7vS+ zJPb!w4CA~YcO#VRUs|9UXj%e#Zw)#(TGXTZ30=TLcG{kC=|H`a_xnP+nICzG=|Bx1pvY->%7Dn3d{h?y->W5H~>qu;35V}oxs zY@>-%@?Oe2C|}N2JAk9#)u(GvZ$7Pn(>QO?5@_eg*UyTVKfWrSynOoX_0G;;_?F@R6V|QRLSdFoBD;4{5%nH@jDz3K*Uz}>BgYk|JFBU?SO(e@bhgTX9bu$zgou8h};!l8WK<8oWzN$FYl29d2}1Foe^lnZv%^%OrN7LJ%^# zYCAs|CtcNxnhegu3`+H$DTk=MFy|qrTI$MEM;pj<1}w0`1YM0O=t&1ZvHIIh225|K zv?cHYinE_`P9#T{u0mOgXt=J>4hr<2o@Oj_m zL#2-K4u5F<*@ocWq*z=uj$i<0VXJOMq8pWi;xQPB!gvPOK5~odgAMdKQFHm97i6MEM>FG2cKV#g{ZF55~p)F(BdI1LT!mfd@q$ z7KTt}T)0uVF~K2v23rFSpgnD=&K$H+BN}!nh=^{-kU?TQb&X7<+?W>nthjDAZsnFi zeRT~R15Ad7psX+6xgEQ{-gSp3N5--c^WIYk5&kT>Pu*bPK43a`Wkh$5iJ;{B(1&#+ z*Q;^^ghx;$D6KQZRQ-MHKvI3cM8*fjH((O_M?9aNOA6M>4-Rh})_k*%A7z-BBzIjD zeZ%DMzwaP_z)sr9Qzm({XprDN+`U|Wi{$*k%Xkv9gF|`(U0t&G9~@bTAY|+bGArl0 zgdKUcuIc_S9U}Zm$aG1-geMI8J7b=}whL!Rsd>1N(-_?uH$J&M<~>hF=rIMTA6@+L z8u?cf-;d9>28KYjD@9g^$ktR#JhxU{8N<6fItA_H<}_I+0C@dLC$v$8Pu3H@sF?b~ zGxn=1lnHLuaZ5FV|M}GkFM<-6`nyFc32@MKL(Tq z@{zO)?nlBuklL;lmO}D@r0PV|e&$y4Umf3i={Pced>&3vlT3xxgvG1MT#n(f2s@e& zFKv}ir_85}4Yq$}L$=+NTO%;zzcQ=Fti5Ya4X@A!KJRzWrJ?drc%qKx6h08gnmhpc z%A_n#=jHN!XaJ}aEYA|Gey^%saOOkO3TFz6Kqkni??mN=lvi#SQ2vF`x}1Yd$!iG= z!Y{^-w9PAbeg*%okjX`ND<_3X-_lZ_hKf7ZUiW`ipF0+%0Hg4Rj$1LsD3{nL52Rn2 zZ2KFJaoMM@o2)wN;YePLaT^#c!jKQWW<|yl-}A7pKh7kF@FRql;&3%V_N)+ z*DYC~{&9QB1Eg6*%ovZs5k1!iI-D$BBxz;=UpC$a=s-%?IZ#^#KjpT z6e*s(AD6sKM;2_YJPS|T6n&W;dCJh%U*UvOWOu}w3R1ulKZ|-}8XacFY%)W^(#a^O zjW7Uw&e8AZXI{Ah)bX?eCW<9TTy!@skq+X>xD-=gr&wuuw0KgT&CB!L!K4}5%)k}5 ztH&Q+XOE#fOk)8&PrF%Hp*mipapsnmQt_a}I*3W0%f+-`r%10Zom4vu)l24#Nrg$= z@)C{}vDQPzI2dZ(t~>#x<=F!y&HElub{L=@ z^G9%^UCft*^1oXL%poDrz$Mi4e&8OLPYcJ`r)6|Uy< zpMmfv^M~*@&zajEj(WEp#>#c_g&S52+7~SigXCNmv--?Q%qSAE=&Tw5X>K9GokAoL zO!CAa&#as@lnmMn@hX!wzw%2JQ~bCh;_-eYuJu^_D5khn@TvQ&%< zpG?5=dKfTeoEpe|)iWRWZV5=T3qae2z95iY26Fuq2E_H$Z!zi*gHE1B&a{M zz{&buCd{*M0lx$|Y;Vg8;k3X#MwjyWHmy1UJzD~gDyK1q0Sy-Q3k*fmdTp?K`&5N2 zray=Nz&JPa%jze0#_@upjn%R7=3^_Vad;)fHOABkwQfYLTcVYjI}IDdV(U^eO4)OX zQ5V!>(4@xc?gVe;1h0ZhQWNtHhZghREL`=%rj8Wqx696{=r<#Wymy_!y0}+a4GuKy8!1dUYL-igBhxgM80xT8{)cg) zi*l_|3|_Jhcwb$&Lr&rzxfFx@zIg!>2dl(W$s{f6MjM5Jlm?>YZ(Z|vHp>jOYZ$n> zPn31f+JJ_Xk>Kbry&bD_*iDxLKTF0{J!OU&esT@-9LR-VGDK*52`OM~(=jBb^?A>Q zYK;bKZB)iZYDPzK-Bj?wBR>rj&ES*mo3nWGoG_I|LM8$HWCnx7jLxE*hKv`cuM)>F zna^HD@gko4m>h@?lt{+swxr37AiPgTyGGk~NUdT>sJ_T*Ns>!ECUBN~DU^61i_AmC zvKgAfmVeA%#!9pol>?W-PkNsQ#y~?T7!QkY#8bqDuyHZUu89Yy6wyFkctY%9E6S%y z+)Z}F8yLf@@8rrXG7QApI{Rt{I|KEG)kHjvg0R`uiQ>VMAREFh2j2?t`z<2J-!Rxr zFtXfM6X)S~)W$oC-i`C}4#e-`hI}YREO+$o7uva`cx}|{dUl&{HSnLntNN|LH`C~W zhexMo$~iq7EAI?f490DwKoSka(4B;scn(bQMixGi3Mc`dXe!v+jbgF zRbnqSu{ObfK3SUTA5`ypGJMme>| zv&A!u0?q|-LRgdu_C{=4kyOo2!mO3OCJd{M+FQ_X=(Sc?PKo9>S@d}YmXEPO5b6zM zx}b=xREq=H-EHP!Hl!NvnMIj|HH}o zY<35_a0ybdQMi0Eahadg#cL)8g5hb`Z@Fq;1tUZ#0?G?8>_!9uoF%1 zXP~g3irGN8!(Kr~NW}`Is!Dg(x1Itq*|Z85(q#l1V~A*~J?*tTmWAn8w1y(7(@W3j zN&f>@ZeTDYa<~a>{y2VB)S@4>$O1B`J5es0C|56CZhNWZ7QQl?j5c(gW9-YlPxTw+ z-K{*#eD}Un?Uh}47fGm&mWme~h}ch%<2``IWXI-uC9(x*bK$MK(k0fGJ#h*nGCZUu zH3>z0wpgGAZ01{DaQI}J(KYL>l|vVuklA>3hz(21bY?^p)?mGnTOHq9wxgiclHFz~ zF^)Ru)X}m9(t*?Qm5nVQ+lr$=(Hmtxj5dVu#vk|GPeYpqS;}N$Mr=!ps}N4}w2WnO zdRxXGr4X_Dh+Jr9u^7wxru7(x-B7mIG0q3=wmpZKLH2;8NalhGcmx3jlQarEDD6Uk zrmx|YidI^g&gG?~o8!Ft=-MhmITSK1RU)LRl{e0Pz1K~034ugh1y33Tt41Q?ICt^LIDRT~K+mq#W zL24ywRoqi>gbGWxAj!Zc8MiZI zNVfy%#L$lTek_Wph)}2y4I?MVak17Yy?NmeWGD`( z2zzEbZA8U(@r?#=_WEmk13KXUHWjLgrj;eD%wR_{RPlzwX|yDzg6ac((6$jtXl5^j zrVDx3=oF@07zOzdt-+T+&N%o>P^%sg&-2Nbh~4{A@B4#-o!`d5T0ul;4he$ZO>P!L zbn(2{v=k7J8DBHKn+op*$#z2Vz zXM|wQT3%h-T-n&Q1nG~pt-Liff!?NSAZ4cx|D$(31b(NqWIR}t1l`fhiXfd~TXlrY zC>)F(Gqp^|$}cJc72wvVjPgG#kDIvTcj+Ia-}FYlNo@BeBF(M?-0s-b>@7eOv1Q_{ z>(;eAzmnb0H!CaTA!5fR(skfN5I`XD_*_|zICLz#sf?exx#NqtL7jZ z4o-JsQlGP+2}rhck|z}B{(J1~G}Yl5XO2&{H_u+Bs-YzFlGWj1euvUM(3|W{XP+b{ zw`lR+rJitCC)?L|(VvP#Zx|;mxI>3GOOe?v5vPQr3pma%Ru1AT0UU-T#49U|c1-zF zQICbu=P#;7Xv1y(DI3r&ni)8Cw9)04HdU2X4E4zjpp+0Wh-UMqS-D^9`L)laq9~z& z#8SlPsi7yQyyUMBKZ{m)*9M^XG2uuk$7POm2;TJ41{x;NhRfR!kjo-E`%`Mcck{QS z7GiVp+0=qwQAcRENG;BMB`1*n5Cr!<6)zzaz7^p}=!S~Nv(5$NVeo-Bn#gW`#d+Qt zDq+TJh=!sDuF(IiuF*|BJ-DF&wNKaVv58MFK|sYCh8$b9>hKk_ZJ!&&3^Ae`Sef(y zp$w;_V)G=|wN56Mu_VYgl4Ttj5a9+Jt7dXec!)*X6k)to@6*USMHP(GUn&*vZ8M5u z0zuo#Sy|6@wnt?Bw`QsPtrMB1#%Yq+GlXmLKGLkVg%YYxPwr4j8yX5m6NNfCbHpr}48C|!QnUDQ-A3uu1=P)1f69+==r>QD0_6DTm9LQn3` zv=5#7GXc--%{hCre%ju$DL=O+{Y*xbP4>AZ-DiKM&s>tvzA%e!Cr2hk4svE}sqJCU zE&feeLF+gxC~ZXD2Jgw=%7|%*aq><})itfV^O*@Q$ zZ!~fg05l;$e%P=MGNzOOFhW_av=rZBEu9yIBZe9T=mQa@#~daZK@fyfIxIisu7@?Z z4cLiS?@S?CaIR;MBaB@x!+eG@{!fZ7xc3951yvgvu8 zZ68HrAgxAb2y8zY@FCH`2~*Y2tczKgu|;I2-4+t_PcN)(N$d`P>Zj+%hJEO(WTWh@ zEE<+183ZTkYV?YdmbJg7hU_((T(=F*6)w6=NP+?**j3h_!tH~M}j5H@{ftQ6$G zZmrGAFxX;|RHDxLpmRPv90s53&(xBS8;1#u^)sg}-boU+JSFoIonHmKz0Z9ZGn5xW z9=<5ys;80+Br|M9!VjSZr%Fd6xm-9ar&pps@5rCap%7AbjOMgMCOmPk)rV=dqSTLY zL%AqxjyDHI(baK^A}nMKpnj+!%IR(L5_tBf?unJBL)a2-^2q6i(@w*e;V$-cf2Y?z zYGkn9@pA%?A{oq%y<~?b%cO$DB-p_%u~XPp>yI0>T>)>x+r+LvL<%?pft`5({Eo+L zE@7gOxECQ43wz&t2whbn`kWKy;!P8=PnUMi2DkWD*6 zkN&_&!8#Q~Yh1YUbe-yWo6sv;tl*78&moEBwAq-fRDp&_yd_8A-qSs+=gS(G?jz79<_qGx@s(`H4XFIC2XRwF;~P)s~~w2ji=EJJ7Ek7=#;0}`vnykc^q z%+3xRH)aG2{*@66bq+E~B#*J0qj^gX6>4PR?R)f&|gY5WW(cqzZ_~g_NNq1>hMbC>H zsg+|;8+gNK>+}l$`nCW$8^5c_^j)&5JUDle@~f8qY=Oha9*6VZWef^mo*z8-+2j6P zWS<7m>=oR^DQEITJHhr0AIyD2k1-xqti^3E)uie8SJI(vYzB;!&fy#d$fB4$#?=@{ zYz7QVCaA9n?_r8x6z0A5n)47R%~HywGEB?3pos#cMSmF{&8qqnB+6QUdp>MW6r)j}n;87u@!R|N2Zfs-5QR4-Y{o`-aB)qLc zm%?(MbvMWM{mSoGu(T;^Y4PO^9)8hGnn`61}b znE(fmCNGUw8BZ>QnqX`C{oBIja#| z_vB(om`4Sv=}(Gng=OrHcAdL2LbM(`nDgmL9=H-TD5W{81{|5l8~{W^lR^vH0hjhc zBt3VW#7w`?>rZ;L)Vt3k;uLQub752bGlb5@Rhj5Rf`CU#zk~sBQbF-Hh`V-36-eT= z8aCmhXpS+lHn8ZTDi4M~{C~7lERAdkH4gXQWe@B6KNEcLniOqDqfkBkj{cqb7-|Sdb1>~y`q#^zGbnvLfqetQ( zFgI;81Eu0e8y#Sx&7j!9%-0{rkd1z#VDNyG3#CO85LuaA3^zO)x?X#QQL0A^;FPuZ z;D7k^=5Fg+qYAQE6fYkEi|l6`XDg4dJvFC{Qo*02eox$fk>AQfTvb7fKiWiJp!C2~ zTm2-;sI=7LiWYG6a$Avz#YS+fju8pP_ zKdoW5!e~_j!vw&X8|dznR22n(;(t)s?j(yom=E`Fd>Yl?V3R0TD#d$yQOTvhH4GZv zbW1mn5wJkVR@Z7Ig(}^aNEj%Eq`yy|;$iC56ALT$k88{UTR}vhtc_+%z&;F#%}94U%fp=s(b>fI^ZKHr z&=iJat#}j`VjdIe6T{VNVJSQ`*%1D6a9fLxPkKN;*_7i8IPaBjFq8>jz@Q;LMiHSU0w*Dapf2`j)f)!qrDoQBaMq15I>c#_*@0t1+!%7 z;_uU}%WpN#!(WRZEop+oUoCO%h*70BuDD!@ zXO1X)BeVKg7TRAu6u%aKu8vNakBN=sW59%?8q5}()e`e9nEPOJZ`E7fx5rG4SrJ0Y zztWL(N&g<3T#PVD$5jG(zJ-~Jex+s&mlA-Vf@}Vgfo~W#-Ca^CHTZZPtfDkO!(q0- zd-FhVTHxn&OtUh1 zEXm3dx=@nHqBuAcs6uJ98i_?uLsMNFCkhWG3#aY|4cAa)bU&m&L@Dbj&YI zA-cj=L8IF~U~{MA==J4x{M-+R<|nE=`YjMO-(gR=d0JZ#%-@E*x9rbPUu+wZw@-!{ zufMtswRFzN7|d1BQ$q6xRIgP`oZz42|0>e>*l@!n_`?||M@JIV9bau4Bc^clAubyh z!&+1T^d@=G4H=J5Fbt+(h^2Q{h1bL1bD6Fy>@cAc@P9>c=X^k$z7)4b|J6V+e2E{) zpLVQVJD0~Tx#c{Ek(|(GF$nerCiW{B1bA^3F{nbGMDc4(Bh#wUMe3F?>D(H8 zzvLo&uo5*LA8xHd1mg7KJ_&l?UXl_l zT+Nb9vPz|_ydFNudLSKjZUG{Z#KiC8lCG}aW56Pyw57rdpWQ3}{u!S^P~&p?V${-YgV&oVJ@&L4|z z-C&;s)zR5PHn;Eqv8SPPL3Ct(1$YD$y4{)h?;xCOY`t20oN`wn(%7M`H<{kJp>R3R!8J#xy+EkE5ZIzdoo>!L*^se@6(lJpo-&BaDV06QycS}VVQ!{K4YcpHu5h`hj0{*{$qd(<>47sd zwB-(jq&HB%UFO{%+r~_J#QCm29Wi%7ckUBrV_{W32|qi*pCN&PYI;cp$l#(^17lfs zSAgV#S=DwQi>ga=wFJYrIa;DLo@Utj=-wSs+@6-OH*le(Bsf;pv0yl3DP8^Ff)hk8 zAz}m)L!GyO{%&$mYFsi)gj~u65s3NtGTy^3q?3P05bI#zjVywNS|jr1&4|7u6-*$b zZmG`X^v!29bZq+;xntVC&fL>OmdUx`yWqtD8iTWNd|eebN<*Mxl2)6~s~)pRU( z^kjuVnIPEj7(%*)-~IEu!rs&#Y|gEj_UdN!2b-!~Lxr_R^=dj9i4?vJRkPHa-E%@2 zfShd_9dnDG&P8-HSf?lrzon}WuI!UNRv25{yigXU2aCrTSJ$?&ed#K8G1j0%?gem; z)GoS-Ulb#MmeQ@YOyZnowgRC+{AH6|(#hH-ky$-qW#%WrUZ65WiVh1pU7HOd?v;^q zigupePI3tsO`^yZ4aU-0>__=b0%k8|Mw)PHB?f9U9K@9OQ7>_uqd;K%FAuf=2_FMA z5*M%Zus4VP6lDrc#jG}h*8;0#9N|i6!&cBJU10GXmbow9)1PGLD@p?4BZYuT>8$*E zO1N^H++al}{yA@3{1C;f>FG|FBiOVeymQ!j%9Ps_c^r@>2b~eBHin2!!`Q~UhUHaA z0fp%nUFZx#d3#P6wKjK^qYlp4iWE)0-dcr4bk} zfa~Kw@cJu~2LE`>=>uXhFBVQkq2#BhYPrCyaw z4=H#bk~;Nd$xNRWGP>(Z8xuH+?6%wKWNCjS_`1u9b5VfLhcaECU{_3~+jWJk@d--P z-jH(2>wOHAsTRBe)yhN zWtnw>otRRPkKhn(h-eSfvN!S=Wu_6k^~f4{+XOjhR?qijh+^kpnD!<)lpGSzjgXe{ ztPImmRWyF}J#^PJvNtJr3t0h5m3kGfl=eqOzis1E?&*>488Ad|RFjX2Y7-`r$ zM-N+-hhCkDre)#Sbna>a-&n6S6@eUpu5xG6sm+A!)n|(HNvjiL*Vy2eI{fbtRY48k#-YmsT z19dT;-Ly|Xk0_p|OkOjg*<=W}_aPPz!nkxj zSMA!Poxe7J1T)ZovHAR3wZT`uXw;kVq=uv9+Vh9syJyplBG);R!J^l#W1O>^Ug)!b z*464;cwXKNJMf2Se)QFuK7NURN}aP&Kitt^ z0XT{*f?nc8S+zD7-i0&nEI(kN?^PeA`5MSEqi)U55wf}x0lD#Nd)JAA4gN&LQk9a;oI-vy zht*nh!I|M|y%`l!Ht!~T-8Jan1k0IRgVPjsfzu}r#CYF7eH48R&75&RdyI()1ZBv? zI=m?^7`=+x@$JT&_gz1^w5s43kGaHKH~Jb&V|^<&vvY-4s$_;;%iDy_ojwYc(aAm5 z^X@Fciz;YQEY`qKxg#7fbfu8x)w1{ud#r&AS9(ZrK5Cyd!Kkuk9BQC6Ut$(Xm^(!& zO`k5z1~?kQ5UQBmTq|0SV%^eo$EU@eF)GKCj`znUS)h^3Jo0^fOE7sI*GG$<*i|1y zcTBW!66;KNkUe)&MZ&P_-5>Oz0WLT_PH#@`<&Ua4UM9L@gZg?GC44>vqrdb&vcsF_`%)&eI zsZ{!2XZ7uB^s+5RbtmpsF(|Bzu!-z&aMhTgAZkewrNn##6Ad|{)2o+gW=M;rocfQ$ zTIW6f2@h-y?;~xvG)-gt_yk;<|L^W9EYUklyCrml>(|f~6a<j>9`1*0lJSmWW91#~ppDcKN^2-H5X>=!ZE!*BfYg(m%lsJ7R zCyl~npFy>#jx3^t>I@8l*5yOkzCRKQQF#a5F;}q6{*{zO@jU26X%E7rGgZgJ(z;u=)Iwa4D`kuPpk2^-7{64fmfU^(# zhRgQQ?mDewDh1&d@<21Tk5q@Ov%ZzY_w5bZ$t54 zuX}@W*U$;1bBHenbC0;he-dnY*?k~(LRt*iPu`c=1ZW`Gr`N<$rZp0v6R6G8WLFxa z5bjHk?+1>nq%mx}Q}VQJ0(=Fpd502d5`$ z8jYyMhF_L*596dh_ZJR(y?J=sv3c{fgI0;m)F#E6rHqU>=>!ef%Tm{$wB#!x+mZMy z%rlzA;pm&k~0+SiOHvsP(@g1xx_O&jc{rLrJqNF|g|s;eRw-2vgJ;F>u$TjH4p>^rlc40b~@KT&K=D`@`ZaU7fJQxb_*e z!Bq~+pF+GXw|}>$8Q-QKS>h;MdpPX6<4o)cC0ex8lwgJiXzR~Y5BMORgs`A(U@2P6 zH-A+sEc#+x@&2cIK9jm>5UsfOhtPD6X(Xzgec}<)%lEB_+ITkNhvw~4YayUy^GdFjztJ3pl$qzjb z-lsgT^J=`Nc=@D4%l&b0FW!eMZtJ8x8w__7W+S~)D85>g$D-@Zv!d2q4I#XG5s$iktGIn-3*oy=TeXRPj1z%$9idY8%wm|+C`Di>eV+sI#~C0 zI|{eWx?A5}&d8Gz(KjBEQMfEScRqkajKp(%G$QV;FA4_TQM5&wVS={ST3+{0oHIrOiHukf>-o4#5ci#Vec_X@;9v(ca9>6PEi||zq z7e9G}#0*z;iYI=-0}AzsJt%y4SNIgQl;nVRuy}bYdKqGKHj=^>JD=2s7PeYJieic& z3{T-nKrB~mTAE_D2osT}@WNX2$Gx@iMIE(EV6_oetx<8934_~jE6m$PUWRB$bosds zZx{aX3*P29`G(grPP#MdG!)SdMK9*}x7&=Cbn>@Oq)MJ!$8bMq`AxWrM z$-mabCt<`alsZ0;bwF-2&eWl^^12Dsz0_>X@s6~)v!7S@a++&CYCHU%u!a?1SClXg za{4(;ndo}0z?OLkq~hdfm1hey<%0Ouy4Swf2|H-PPA@3y79#ktbfEh2^2f-I4#-nA zgl?73+YkerDOw4QOqegCD?fmkVw**_^uzeQs*7WNM6haJXsyCPb)S?wt$_Hvioq}J zM$N6ISH9U<&S)#*Yzg+^Sf}I1*A`MY*Iv{`!i~b*;p&IAU19_G!mP`5CWz*YwWKUY zU+s81>|gT_?yeRWP&ecWg|BfIKagScp7F?z$?`b<$TE&Kh1ou>+&&PfnNaPL%eMGm zBcv$K&w+Bf^nWpI41&PAV}A9OQDcZ zxud8SEJ36+yx*cRF8FK%y^p{U08aoLu}detmQtXOVCj@9`zf)%=*&o&zk`hY z>9Q;sIkCnT?DJI-(0%9EC(8&v4Yqnb(jf}&7GqO>MHM8rVYhwE><)KR7#q1v<*k7d zT8~1?TFQ>gFm4lPVqR(wU3*(M4iY!~P2b`MFiDH(>1~xr*6!vO4Ly0x;IIPS*h~rYYdIixYsZBi15p0Uct}0A`JE?ZPF)ac80#3G+~A9@Rd zA`?f9DYZ^>CKFG}jnRlN&x@aFvaRD+nr$1%3~a=t!!s`)9w>|+5ZQg_*vK$M8{MN2 z_?=KuVuGr13URkhc4HobW?>SWD1XgNjlIPhjFe>AH~gOE(?&>%jUzL4en}v?mj(NS zSuwfpZWgn>BXcxSdu|Ic<>d;`@vBJu$Q)U-q?ZYRaP9&x?7OO!2t2yb@#EK=nHhGG zjqQHxom*MHG>hmfxG3UDQ^LSf&*k`9iBaz-2I#rEVfPfngHJ#>Sk*xu5c3d=7#RvT zU4!y!&ZnO(Wd(s2Lg-F0#QcpiU8U4gpg5!gCxOt&*gV?>vm=4!mo)~Fd1dRPkrDes zIPqv_$HsOZ*SG7PVO}79(5J`2=V>Y%!)$DwIO&$Jo~kSf%_OcB&|5Hdw71+uk)-*N z(<5B?!=rUBh4`=+SAkM_)_3JGUW58d9rj<`Jah>*ODya2hO>4f6$?79AP&jpYS_yq z7+jlmPmN_rh2OL=E%2~SjP-G!s*zOeWq(vQvU@+)`J7{>-ZjnY4!LID-5tI`Do8^> z5<(%o8=(UL+0mqee?R=s0}+7pKPMEx>EGY{b2gr;3iLlr)&Ip%{XY>={z4Rcr~fnY zZ)tn~nff=wcDTRS*&<0XijD~YIEVn=ng6Ks-O?RU6cGRap;|iG+5W=_{y)|H|LC8t z9B5ly0RR|$NC56%=(q2h|5~%Pow0$_KXN7hJ3QB>Vu=np08kPK_!szlLx8vzC3&f7JTFQ5^rB&R^~R|5?X$;IBIW cZvOwjDL})#x5|4z!2zBi0f2UrzxB`m0j6r@@Bjb+ literal 0 HcmV?d00001 diff --git a/dist/miso.ds.min.0.2.1.js b/dist/miso.ds.min.0.2.1.js deleted file mode 100644 index 1385651..0000000 --- a/dist/miso.ds.min.0.2.1.js +++ /dev/null @@ -1,9 +0,0 @@ -/** -* Miso.Dataset - v0.2.1 - 6/29/2012 -* http://github.com/misoproject/dataset -* Copyright (c) 2012 Alex Graul, Irene Ros; -* Dual Licensed: MIT, GPL -* https://github.com/misoproject/dataset/blob/master/LICENSE-MIT -* https://github.com/misoproject/dataset/blob/master/LICENSE-GPL -*/ -(function(a,_){var b=a.Miso={};b.typeOf=function(a,c){var d=_.keys(b.types),e;return d.push(d.splice(_.indexOf(d,"string"),1)[0]),d.push(d.splice(_.indexOf(d,"mixed"),1)[0]),e=_.find(d,function(d){return b.types[d].test(a,c)}),e=_.isUndefined(e)?"string":e,e},b.types={mixed:{name:"mixed",coerce:function(a){return a},test:function(a){return!0},compare:function(a,b){return ab?1:0},numeric:function(a){return _.isNaN(Number(a))?null:Number(a)}},string:{name:"string",coerce:function(a){return a==null?null:a.toString()},test:function(a){return a===null||typeof a=="undefined"||typeof a=="string"},compare:function(a,b){return a==null&&b!=null?-1:a!=null&&b==null?1:ab?1:0},numeric:function(a){return _.isNaN(+a)||a===null?null:_.isNumber(+a)?+a:null}},"boolean":{name:"boolean",regexp:/^(true|false)$/,coerce:function(a){return a==="false"?!1:Boolean(a)},test:function(a){return a===null||typeof a=="undefined"||typeof a=="boolean"||this.regexp.test(a)?!0:!1},compare:function(a,b){return a==null&&b!=null?-1:a!=null&&b==null?1:a==null&&b==null?0:a===b?0:ab?1:0},numeric:function(a){return a.valueOf()}}}})(this,_),function(a,_){var b=a.Miso||(a.Miso={});b.Event=function(a){_.isArray(a)||(a=[a]),this.deltas=a},_.extend(b.Event.prototype,{affectedColumns:function(){var a=[];return _.each(this.deltas,function(b){b.old=b.old||[],b.changed=b.changed||[],a=_.chain(a).union(_.keys(b.old),_.keys(b.changed)).reject(function(a){return a==="_id"}).value()}),a}}),_.extend(b.Event,{isRemove:function(a){return _.isUndefined(a.changed)||_.keys(a.changed).length===0?!0:!1},isAdd:function(a){return _.isUndefined(a.old)||_.keys(a.old).length===0?!0:!1},isUpdate:function(a){return!this.isRemove(a)&&!this.isAdd(a)?!0:!1}}),b.Events={},b.Events.bind=function(a,b,c){var d=this._callbacks||(this._callbacks={}),e=d[a]||(d[a]={}),f=e.tail||(e.tail=e.next={});return f.callback=b,f.context=c,e.tail=f.next={},this},b.Events.unbind=function(a,b){var c,d,e;if(!a)this._callbacks=null;else if(c=this._callbacks)if(!b)c[a]={};else if(d=c[a])while((e=d)&&(d=d.next)){if(d.callback!==b)continue;e.next=d.next,d.context=d.callback=null;break}return this},b.Events.trigger=function(a){var b,c,d,e,f,g=["all",a];if(!(c=this._callbacks))return this;while(f=g.pop()){if(!(b=c[f]))continue;e=f==="all"?arguments:Array.prototype.slice.call(arguments,1);while(b=b.next)(d=b.callback)&&d.apply(b.context||this,e)}return this},b.Events._buildEvent=function(a){return new b.Event(a)}}(this,_),function(a,_){var b=a.Miso||{};b.Builder={detectColumnType:function(a,c){var d=_.inject(c.slice(0,5),function(a,c){var d=b.typeOf(c);return c!==""&&a.indexOf(d)===-1&&!_.isNull(c)&&a.push(d),a},[]);return d.length===1?a.type=d[0]:a.type="mixed",a},detectColumnTypes:function(a,c){_.each(c,function(c,d){var e=a.column(d);if(e.type){e.force=!0;return}b.Builder.detectColumnType(e,c)},this)},cacheRows:function(a){b.Builder.clearRowCache(a),_.each(a._columns[a._columnPositionByName._id].data,function(b,c){a._rowPositionById[b]=c,a._rowIdByPosition.push(b)},a);var c=_.uniq(_.map(a._columns,function(a){return a.data.length}));if(c.length>1)throw new Error("Row lengths need to be the same. Empty values should be set to null."+_.map(a._columns,function(a){return a.data+"|||"}));a.length=c[0]},clearRowCache:function(a){a._rowPositionById={},a._rowIdByPosition=[]},cacheColumns:function(a){a._columnPositionByName={},_.each(a._columns,function(b,c){a._columnPositionByName[b.name]=c})}},Array.prototype.indexOf||(Array.prototype.indexOf=function(a,b){for(var c=b||0,d=this.length;c0&&(a=this.numericAt(c));return b.types[this.type].coerce(a,this)},_min:function(){var a=Infinity;for(var c=0;c=0;c--)a.apply(b||this,[this.rowByPosition(c),c])},eachColumn:function(a,b){var c=this.columnNames();for(var d=0;d=0)f(b,a-1),b--};if(c>2){g(c),d=c-1;while(d>1)e(d,0),d--,f(0,d)}else this.comparator(this.rowByPosition(0),this.rowByPosition(1))>0&&e(0,1);return this.comparator(this.rowByPosition(this.length-2),this.rowByPosition(this.length-1))>0&&e(this.length-1,this.length-2),this.syncable&&b.silent&&this.trigger("sort"),this},toJSON:function(){var a=[];for(var b=0;b0&&_.times(a,function(){b.push(_.uniqueId())}),this.addColumn({name:"_id",type:"number",data:b});if(this._columnPositionByName._id!==0){var c=this._columns[this._columnPositionByName._id],d=this._columnPositionByName._id;this._columns.splice(d,1),this._columns.unshift(c),this._columnPositionByName._id=0,_.each(this._columnPositionByName,function(a,b){b!=="_id"&&this._columnPositionByName[b]1&&(f=b),a},{}),new Error('You have more than one column named "'+f+'"')}_.each(a.table.rows,function(a){a=a.c;for(e=0;e0){var n=0,o=0,p=d.length;while(nb)return 1},numeric:function(a){return a===null||_.isNaN(+a)?null:+a}},string:{name:"string",coerce:function(a){return _.isNaN(a)||a===null||typeof a=="undefined"?null:a.toString()},test:function(a){return a===null||typeof a=="undefined"||typeof a=="string"},compare:function(a,b){return a==null&&b!=null?-1:a!=null&&b==null?1:ab?1:0},numeric:function(a){return _.isNaN(+a)||a===null?null:_.isNumber(+a)?+a:null}},"boolean":{name:"boolean",regexp:/^(true|false)$/,coerce:function(a){return _.isNaN(a)||a===null||typeof a=="undefined"?null:a==="false"?!1:Boolean(a)},test:function(a){return a===null||typeof a=="undefined"||typeof a=="boolean"||this.regexp.test(a)?!0:!1},compare:function(a,b){return a==null&&b!=null?-1:a!=null&&b==null?1:a==null&&b==null?0:a===b?0:ab?1:0},numeric:function(a){return _.isNaN(a)||a===null?null:a.valueOf()}}}})(this,_),function(a,_){var b=a.Miso||(a.Miso={});b.Event=function(a){_.isArray(a)||(a=[a]),this.deltas=a},_.extend(b.Event.prototype,{affectedColumns:function(){var a=[];return _.each(this.deltas,function(b){b.old=b.old||[],b.changed=b.changed||[],a=_.chain(a).union(_.keys(b.old),_.keys(b.changed)).reject(function(a){return a==="_id"}).value()}),a}}),_.extend(b.Event,{isRemove:function(a){return _.isUndefined(a.changed)||_.keys(a.changed).length===0?!0:!1},isAdd:function(a){return _.isUndefined(a.old)||_.keys(a.old).length===0?!0:!1},isUpdate:function(a){return!this.isRemove(a)&&!this.isAdd(a)?!0:!1}}),b.Events={},b.Events.bind=function(a,b,c){var d=this._callbacks||(this._callbacks={}),e=d[a]||(d[a]={}),f=e.tail||(e.tail=e.next={});return f.callback=b,f.context=c,e.tail=f.next={},this},b.Events.unbind=function(a,b){var c,d,e;if(!a)this._callbacks=null;else if(c=this._callbacks)if(!b)c[a]={};else if(d=c[a])while((e=d)&&(d=d.next)){if(d.callback!==b)continue;e.next=d.next,d.context=d.callback=null;break}return this},b.Events.trigger=function(a){var b,c,d,e,f,g=["all",a];if(!(c=this._callbacks))return this;while(f=g.pop()){if(!(b=c[f]))continue;e=f==="all"?arguments:Array.prototype.slice.call(arguments,1);while(b=b.next)(d=b.callback)&&d.apply(b.context||this,e)}return this},b.Events._buildEvent=function(a){return new b.Event(a)}}(this,_),function(a,_){var b=a.Miso||{};b.Builder={detectColumnType:function(a,c){var d=_.inject(c.slice(0,5),function(a,c){var d=b.typeOf(c);return c!==""&&a.indexOf(d)===-1&&!_.isNull(c)&&a.push(d),a},[]);return d.length===1?a.type=d[0]:a.type="mixed",a},detectColumnTypes:function(a,c){_.each(c,function(c,d){var e=a.column(d);if(e.type){e.force=!0;return}b.Builder.detectColumnType(e,c)},this)},cacheRows:function(a){b.Builder.clearRowCache(a),_.each(a._columns[a._columnPositionByName._id].data,function(b,c){a._rowPositionById[b]=c,a._rowIdByPosition.push(b)},a);var c=_.uniq(_.map(a._columns,function(a){return a.data.length}));if(c.length>1)throw new Error("Row lengths need to be the same. Empty values should be set to null."+_.map(a._columns,function(a){return a.data+"|||"}));a.length=c[0]},clearRowCache:function(a){a._rowPositionById={},a._rowIdByPosition=[]},cacheColumns:function(a){a._columnPositionByName={},_.each(a._columns,function(b,c){a._columnPositionByName[b.name]=c})}},Array.prototype.indexOf||(Array.prototype.indexOf=function(a,b){for(var c=b||0,d=this.length;c0&&(a=this.numericAt(c));return b.types[this.type].coerce(a,this)},_min:function(){var a=Infinity;for(var c=0;c=0;c--)a.apply(b||this,[this.rowByPosition(c),c])},eachColumn:function(a,b){var c=this.columnNames();for(var d=0;d=0)f(b,a-1),b--};if(c>2){g(c),d=c-1;while(d>1)e(d,0),d--,f(0,d)}else this.comparator(this.rowByPosition(0),this.rowByPosition(1))>0&&e(0,1);return this.comparator(this.rowByPosition(this.length-2),this.rowByPosition(this.length-1))>0&&e(this.length-1,this.length-2),this.syncable&&b.silent&&this.trigger("sort"),this},toJSON:function(){var a=[];for(var b=0;b0&&this.add(h)},blind:function(a){var b,c,d=[],e,f=_.keys(a),g=_.max(_.map(f,function(b){return a[b].length},this));for(var h=0;h0&&this.each(function(a,b){e.compute(a,b)},this),e},addColumn:function(a){return _.isUndefined(this.column(a.name))?(a=new b.Column(a),this._columns.push(a),this._columnPositionByName[a.name]=this._columns.length-1,a):!1},_addIdColumn:function(a){if(!_.isUndefined(this.column("_id")))return;var b=[];a&&a>0&&_.times(a,function(){b.push(_.uniqueId())}),this.addColumn({name:"_id",type:"number",data:b});if(this._columnPositionByName._id!==0){var c=this._columns[this._columnPositionByName._id],d=this._columnPositionByName._id;this._columns.splice(d,1),this._columns.unshift(c),this._columnPositionByName._id=0,_.each(this._columnPositionByName,function(a,b){b!=="_id"&&this._columnPositionByName[b]1&&(f=b),a},{}),new Error('You have more than one column named "'+f+'"')}_.each(a.table.rows,function(a){a=a.c;for(e=0;e0){var n=0,o=0,p=d.length;while(n s2) { return 1; } - return 0; + if ( _.isEqual(s1, s2) ) { return 0; } + if (s1 < s2) { return -1;} + if (s1 > s2) { return 1; } }, numeric : function(v) { - return _.isNaN( Number(v) ) ? null : Number(v); + return v === null || _.isNaN(+v) ? null : +v; } }, string : { name : "string", coerce : function(v) { - return v == null ? null : v.toString(); + if (_.isNaN(v) || v === null || typeof v === "undefined") { + return null; + } + return v.toString(); }, + test : function(v) { return (v === null || typeof v === "undefined" || typeof v === 'string'); }, + compare : function(s1, s2) { if (s1 == null && s2 != null) { return -1; } if (s1 != null && s2 == null) { return 1; } @@ -193,6 +200,9 @@ var request = require("request"); name : "boolean", regexp : /^(true|false)$/, coerce : function(v) { + if (_.isNaN(v) || v === null || typeof v === "undefined") { + return null; + } if (v === 'false') { return false; } return Boolean(v); }, @@ -211,7 +221,7 @@ var request = require("request"); return (n1 < n2 ? -1 : 1); }, numeric : function(value) { - if (_.isNaN(value)) { + if (value === null || _.isNaN(value)) { return null; } else { return (value) ? 1 : 0; @@ -221,12 +231,13 @@ var request = require("request"); number : { name : "number", - regexp : /^[\-\.]?[0-9]+([\.][0-9]+)?$/, + regexp : /^\s*[\-\.]?[0-9]+([\.][0-9]+)?\s*$/, coerce : function(v) { - if (_.isNull(v)) { + var cv = +v; + if (_.isNull(v) || typeof v === "undefined" || _.isNaN(cv)) { return null; } - return _.isNaN(v) ? null : +v; + return cv; }, test : function(v) { if (v === null || typeof v === "undefined" || typeof v === 'number' || this.regexp.test( v ) ) { @@ -243,6 +254,9 @@ var request = require("request"); return (n1 < n2 ? -1 : 1); }, numeric : function(value) { + if (_.isNaN(value) || value === null) { + return null; + } return value; } }, @@ -292,6 +306,11 @@ var request = require("request"); coerce : function(v, options) { options = options || {}; + + if (_.isNull(v) || typeof v === "undefined" || _.isNaN(v)) { + return null; + } + // if string, then parse as a time if (_.isString(v)) { var format = options.format || this.format; @@ -324,6 +343,9 @@ var request = require("request"); return 0; }, numeric : function( value ) { + if (_.isNaN(value) || value === null) { + return null; + } return value.valueOf(); } } @@ -678,6 +700,35 @@ var request = require("request"); }, this); }, + /** + * If this is a computed column, it calculates the value + * for this column and adds it to the data. + * Parameters: + * row - the row from which column is computed. + * i - Optional. the index at which this value will get added. + * Returns + * val - the computed value + */ + compute : function(row, i) { + if (this.func) { + var val = this.func(row); + if (typeof i !== "undefined") { + this.data[i] = val; + } else { + this.data.push(val); + } + + return val; + } + }, + + /** + * returns true if this is a computed column. False otherwise. + */ + isComputed : function() { + return !_.isUndefined(this.func); + }, + _sum : function() { return _.sum(this.data); }, @@ -1107,6 +1158,11 @@ var request = require("request"); _.each(row, function(value, key) { var column = this.column(key); + // is this a computed column? if so throw an error + if (column.isComputed()) { + throw "You're trying to update a computed column. Those get computed!"; + } + // if we suddenly see values for data that didn't exist before as a column // just drop it. First fetch defines the column structure. if (typeof column !== "undefined") { @@ -1126,18 +1182,28 @@ var request = require("request"); } else { throw("incorrect value '" + row[column.name] + "' of type " + Miso.typeOf(row[column.name], column) + - " passed to column with type " + column.type); + " passed to column '" + column.name + "' with type " + column.type); } } }, this); + // do we have any computed columns? If so we need to calculate their values. + if (this._computedColumns) { + _.each(this._computedColumns, function(column) { + var newVal = column.compute(row); + row[column.name] = newVal; + }); + } + // if we don't have a comparator, just append them at the end. if (_.isUndefined(this.comparator)) { // add all data _.each(this._columns, function(column) { - column.data.push(!_.isUndefined(row[column.name]) && !_.isNull(row[column.name]) ? row[column.name] : null); + if (!column.isComputed()) { + column.data.push(!_.isUndefined(row[column.name]) && !_.isNull(row[column.name]) ? row[column.name] : null); + } }); this.length++; @@ -1584,6 +1650,7 @@ Version 0.0.1.2 this._columns = []; this._columnPositionByName = {}; + this._computedColumns = []; if (typeof options !== "undefined") { options = options || {}; @@ -1676,8 +1743,6 @@ Version 0.0.1.2 this.uniqueAgainst = options.uniqueAgainst; } - - // if there is no data and no url set, we must be building // the dataset from scratch, so create an id column. if (_.isUndefined(options.data) && _.isUndefined(options.url)) { @@ -1784,43 +1849,33 @@ Version 0.0.1.2 againstColumn : function(data) { var rows = [], - colNames = _.keys(data), row, - // get against unique col - uniqCol = this.column(this.uniqueAgainst), - len = data[this._columns[1].name].length, - dataLength = _.max(_.map(colNames, function(name) { - return data[name].length; - }, this)); - - var posToRemove = [], i; - for(i = 0; i < len; i++) { - - var datum = data[this.uniqueAgainst][i]; - // this is a non unique row, remove it from all the data - // arrays - if (uniqCol.data.indexOf(datum) !== -1) { - posToRemove.push(i); - } - } - - // sort and reverse the removal ids, this way we won't - // lose position by removing an early id that will shift - // array and throw all other ids off. - posToRemove.sort().reverse(); + uniqName = this.uniqueAgainst, + uniqCol = this.column(uniqName), + toAdd = [], + toUpdate = [], + toRemove = []; + + _.each(data[uniqName], function(key, dataIndex) { + var rowIndex = uniqCol.data.indexOf( Miso.types[uniqCol.type].coerce(key) ); + + var row = {}; + _.each(data, function(col, name) { + row[name] = col[dataIndex]; + }); - for(i = 0; i < dataLength; i++) { - if (posToRemove.indexOf(i) === -1) { - row = {}; - for(var j = 0; j < colNames.length; j++) { - row[colNames[j]] = data[colNames[j]][i]; - } - rows.push(row); + if (rowIndex === -1) { + toAdd.push( row ); + } else { + toUpdate.push( row ); + var oldRow = this.rowById(this.column('_id').data[rowIndex])._id; + this.update(oldRow, row); } + }, this); + if (toAdd.length > 0) { + this.add(toAdd); } - - this.add(rows); }, //Always blindly add new rows @@ -1903,6 +1958,46 @@ Version 0.0.1.2 }, this); }, + /** + * Allows adding of a computed column. A computed column is + * a column that is somehow based on the other existing columns. + * Parameters: + * name : name of new column + * type : The type of the column based on existing types. + * func : The way that the column is derived. It takes a row as a parameter. + */ + addComputedColumn : function(name, type, func) { + // check if we already ahve a column by this name. + if ( !_.isUndefined(this.column(name)) ) { + throw "There is already a column by this name."; + } else { + + // check that this is a known type. + if (typeof Miso.types[type] === "undefined") { + throw "The type " + type + " doesn't exist"; + } + + var column = new Miso.Column({ + name : name, + type : type, + func : _.bind(func, this) + }); + + this._columns.push(column); + this._computedColumns.push(column); + this._columnPositionByName[column.name] = this._columns.length - 1; + + // do we already have data? if so compute the values for this column. + if (this.length > 0) { + this.each(function(row, i) { + column.compute(row, i); + }, this); + } + + return column; + } + }, + /** * Adds a single column to the dataset * Parameters: @@ -2066,8 +2161,15 @@ Version 0.0.1.2 newKeys = _.keys(props); _.each(newKeys, function(columnName) { + c = this.column(columnName); + // check if we're trying to update a computed column. If so + // fail. + if (c.isComputed()) { + throw "You're trying to update a computed column. Those get computed!"; + } + // test if the value passes the type test var Type = Miso.types[c.type]; @@ -2084,11 +2186,28 @@ Version 0.0.1.2 } else { throw("incorrect value '" + props[c.name] + "' of type " + Miso.typeOf(props[c.name], c) + - " passed to column with type " + c.type); + " passed to column '" + c.name + "' with type " + c.type); } } c.data[rowIndex] = props[c.name]; }, this); + + // do we have any computed columns? if so we need to update + // the row. + if (typeof this._computedColumns !== "undefined") { + _.each(this._computedColumns, function(column) { + + // compute the complete row: + var newrow = _.extend({}, row, props); + + var oldValue = newrow[column.name]; + var newValue = column.compute(newrow, rowIndex); + // if this is actually a new value, then add it to the delta. + if (oldValue !== newValue) { + props[column.name] = newValue; + } + }); + } deltas.push( { _id : row._id, old : row, changed : props } ); }, this); @@ -2577,7 +2696,7 @@ Version 0.0.1.2 type : "GET", async : true, xhr : function() { - return new global.XMLHttpRequest(); + return global.ActiveXObject ? new global.ActiveXObject("Microsoft.XMLHTTP") : new global.XMLHttpRequest(); } }, rparams = /\?/; diff --git a/package.json b/package.json index ff8f618..3e04f5a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name" : "miso.dataset", "title" : "Miso.Dataset", "description" : "Dataset is a javascript library makes managing the data behind client-side visualisations easy, including realtime data. It takes care of loading, parsing, sorting, filtering and querying of datasets as well as the creation of derivative datasets. Dataset is part of the Miso Toolkit", - "version" : "0.2.1", + "version" : "0.2.2", "homepage" : "http://github.com/misoproject/dataset", "authors" : "Alex Graul, Irene Ros", @@ -21,7 +21,7 @@ } ], - "main": "dist/node/miso.ds.deps.0.2.1", + "main": "dist/node/miso.ds.deps.0.2.2", "repository": { "type": "git", From c460a7f60e4a087bfa8c46fb8e7d3f6b4966a9cc Mon Sep 17 00:00:00 2001 From: Irene Ros Date: Fri, 14 Sep 2012 10:30:13 -0400 Subject: [PATCH 06/12] added bower component.json --- component.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 component.json diff --git a/component.json b/component.json new file mode 100644 index 0000000..b502f96 --- /dev/null +++ b/component.json @@ -0,0 +1,12 @@ +{ + "name": "miso.dataset", + "version": "0.2.2", + "main": "./dist/miso.ds.0.2.2.js", + "dependencies" : { + "json2" : "*", + "lodash" : "0.6.1", + "moment" : "1.7.0", + "underscore.deferred" : "*", + "underscore.math" : "git://github.com/syntagmatic/underscore.math.git" + } +} \ No newline at end of file From a2eef6f41915aefaf6d24976185b9b3635fdcaa1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 17 Sep 2012 16:31:27 +0100 Subject: [PATCH 07/12] Fixed Internet Exploder bug - readystatechange handlers not being called when fetching resources from cache --- src/importers/remote.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/importers/remote.js b/src/importers/remote.js index 7e11d07..868f705 100644 --- a/src/importers/remote.js +++ b/src/importers/remote.js @@ -216,9 +216,9 @@ }; Miso.Xhr.httpData = function(settings) { - var data, json = null; + var data, json = null, handleResponse; - settings.ajax.onreadystatechange = function() { + handleResponse = function () { if (settings.ajax.readyState === 4) { try { json = JSON.parse(settings.ajax.responseText); @@ -247,6 +247,12 @@ } }; + if ( settings.ajax.readyState === 4 ) { + handleResponse(); // Internet Exploder doesn't bother with readystatechange handlers for cached resources... trigger the handler manually + } else { + settings.ajax.onreadystatechange = handleResponse; + } + return data; }; From 4f14ed4cb5b210cc6c1b4e8efe2064a264f57683 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 17 Sep 2012 16:33:05 +0100 Subject: [PATCH 08/12] illegal identifier ("boolean") causing havoc with build scripts --- src/types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.js b/src/types.js index d32c0dd..d91b0bb 100644 --- a/src/types.js +++ b/src/types.js @@ -74,7 +74,7 @@ } }, - boolean : { + "boolean" : { name : "boolean", regexp : /^(true|false)$/, coerce : function(v) { From 542a7bb862712b085f408bd7200029b347c2988e Mon Sep 17 00:00:00 2001 From: Irene Ros Date: Wed, 19 Sep 2012 14:53:14 -0400 Subject: [PATCH 09/12] updating dependencies and package.json --- lib/lodash.js | 634 +++++++++++++++++++++---------------- lib/underscore.deferred.js | 444 +++++++++++++------------- package.json | 4 +- 3 files changed, 588 insertions(+), 494 deletions(-) diff --git a/lib/lodash.js b/lib/lodash.js index c603e64..2d36055 100644 --- a/lib/lodash.js +++ b/lib/lodash.js @@ -1,5 +1,5 @@ /*! - * Lo-Dash v0.6.1 + * Lo-Dash v0.7.0 * Copyright 2012 John-David Dalton * Based on Underscore.js 1.3.3, copyright 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. * @@ -54,7 +54,7 @@ var oldDash = window._; /** Used to detect delimiter values that should be processed by `tokenizeEvaluate` */ - var reComplexDelimiter = /[-+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/; + var reComplexDelimiter = /[-?+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/; /** Used to match HTML entities */ var reEscapedHtml = /&(?:amp|lt|gt|quot|#x27);/g; @@ -78,7 +78,7 @@ ); /** Used to match internally used tokens in template text */ - var reToken = /__token__(\d+)/g; + var reToken = /__token(\d+)__/g; /** Used to match HTML characters */ var reUnescapedHtml = /[&<>"']/g; @@ -96,7 +96,8 @@ var templateCounter = 0; /** Used to replace template delimiters */ - var token = '__token__'; + var tokenHead = '__token', + tokenFoot = '__'; /** Used to store tokenized template text snippets */ var tokenized = []; @@ -111,9 +112,13 @@ /* Native method shortcuts for methods with the same name as other `lodash` methods */ var nativeBind = reNative.test(nativeBind = slice.bind) && nativeBind, + nativeFloor = Math.floor, nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, nativeIsFinite = window.isFinite, - nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys; + nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys, + nativeMax = Math.max, + nativeMin = Math.min, + nativeRandom = Math.random; /** `Object#toString` result shortcuts */ var argsClass = '[object Arguments]', @@ -217,7 +222,7 @@ */ try { var useSourceURL = (Function('//@')(), !window.attachEvent); - } catch(e){ } + } catch(e) { } /** Used to identify object classifications that are array-like */ var arrayLikeClasses = {}; @@ -303,10 +308,10 @@ */ function LoDash(value) { // exit early if already wrapped - if (value && value._wrapped) { + if (value && value.__wrapped__) { return value; } - this._wrapped = value; + this.__wrapped__ = value; } /** @@ -442,8 +447,8 @@ // else using a for-in loop ' <% } else { %>\n' + ' <%= objectBranch.beforeLoop %>;\n' + - ' for (index in iteratee) {' + - ' <% if (!hasDontEnumBug || useHas) { %>\n if (<%' + + ' for (index in iteratee) {<%' + + ' if (!hasDontEnumBug || useHas) { %>\n if (<%' + ' if (!hasDontEnumBug) { %>!(skipProto && index == \'prototype\')<% }' + ' if (!hasDontEnumBug && useHas) { %> && <% }' + ' if (useHas) { %>hasOwnProperty.call(iteratee, index)<% }' + @@ -516,25 +521,6 @@ '(hasOwnProperty.call(result, prop) ? result[prop]++ : result[prop] = 1)' }; - /** Reusable iterator options for `drop` and `pick` */ - var dropIteratorOptions = { - 'useHas': false, - 'args': 'object, callback, thisArg', - 'init': '{}', - 'top': - 'var isFunc = typeof callback == \'function\';\n' + - 'if (!isFunc) {\n' + - ' var props = concat.apply(ArrayProto, arguments)\n' + - '} else if (thisArg) {\n' + - ' callback = iteratorBind(callback, thisArg)\n' + - '}', - 'inLoop': - 'if (isFunc\n' + - ' ? !callback(value, index, object)\n' + - ' : indexOf(props, index) < 0\n' + - ') result[index] = value' - }; - /** Reusable iterator options for `every` and `some` */ var everyIteratorOptions = { 'init': 'true', @@ -586,6 +572,25 @@ } }; + /** Reusable iterator options for `omit` and `pick` */ + var omitIteratorOptions = { + 'useHas': false, + 'args': 'object, callback, thisArg', + 'init': '{}', + 'top': + 'var isFunc = typeof callback == \'function\';\n' + + 'if (!isFunc) {\n' + + ' var props = concat.apply(ArrayProto, arguments)\n' + + '} else if (thisArg) {\n' + + ' callback = iteratorBind(callback, thisArg)\n' + + '}', + 'inLoop': + 'if (isFunc\n' + + ' ? !callback(value, index, object)\n' + + ' : indexOf(props, index) < 0\n' + + ') result[index] = value' + }; + /*--------------------------------------------------------------------------*/ /** @@ -752,15 +757,17 @@ a = a.criteria; b = b.criteria; - if (a === undefined) { - return 1; - } - if (b === undefined) { - return -1; - } // ensure a stable sort in V8 and other engines // http://code.google.com/p/v8/issues/detail?id=90 - return a < b ? -1 : a > b ? 1 : ai < bi ? -1 : 1; + if (a !== b) { + if (a > b || a === undefined) { + return 1; + } + if (a < b || b === undefined) { + return -1; + } + } + return ai < bi ? -1 : 1; } /** @@ -836,7 +843,7 @@ } var index = tokenized.length; tokenized[index] = "' +\n__e(" + value + ") +\n'"; - return token + index; + return tokenHead + index + tokenFoot; } /** @@ -854,7 +861,7 @@ if (evaluateValue) { var index = tokenized.length; tokenized[index] = "';\n" + evaluateValue + ";\n__p += '"; - return token + index; + return tokenHead + index + tokenFoot; } return escapeValue ? tokenizeEscape(null, escapeValue) @@ -875,7 +882,7 @@ } var index = tokenized.length; tokenized[index] = "' +\n((__t = (" + value + ")) == null ? '' : __t) +\n'"; - return token + index; + return tokenHead + index + tokenFoot; } /** @@ -961,58 +968,71 @@ } /** - * 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. + * A fallback implementation of `isPlainObject` that 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. * @param {Boolean} [skipArgsCheck=false] Internally used to skip checks for * `arguments` objects. - * @returns {Boolean} Returns `true` if the `value` is a plain `Object` object, - * else `false`. + * @returns {Boolean} Returns `true` if `value` is a plain object, else `false`. */ - function isPlainObject(value, skipArgsCheck) { - return value - ? value == ObjectProto || (value.__proto__ == ObjectProto && (skipArgsCheck || !isArguments(value))) - : false; - } - // fallback for IE - if (!isPlainObject(objectTypes)) { - isPlainObject = function(value, skipArgsCheck) { - // avoid non-objects and false positives for `arguments` objects - var result = false; - if (!(value && typeof value == 'object') || (!skipArgsCheck && isArguments(value))) { - return result; - } - // IE < 9 presents DOM nodes as `Object` objects except they have `toString` - // methods that are `typeof` "string" and still can coerce nodes to strings. - // Also check that the constructor is `Object` (i.e. `Object instanceof Object`) - var ctor = value.constructor; - if ((!noNodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) && - (!isFunction(ctor) || ctor instanceof ctor)) { - // IE < 9 iterates inherited properties before own properties. If the first - // iterated property is an object's own property then there are no inherited - // enumerable properties. - if (iteratesOwnLast) { - forIn(value, function(objValue, objKey) { - result = !hasOwnProperty.call(value, objKey); - return false; - }); - return result === 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(objValue, objKey) { - result = objKey; + function isPlainFallback(value, skipArgsCheck) { + // avoid non-objects and false positives for `arguments` objects + var result = false; + if (!(value && typeof value == 'object') || (!skipArgsCheck && isArguments(value))) { + return result; + } + // IE < 9 presents DOM nodes as `Object` objects except they have `toString` + // methods that are `typeof` "string" and still can coerce nodes to strings. + // Also check that the constructor is `Object` (i.e. `Object instanceof Object`) + var ctor = value.constructor; + if ((!noNodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) && + (!isFunction(ctor) || ctor instanceof ctor)) { + // IE < 9 iterates inherited properties before own properties. If the first + // iterated property is an object's own property then there are no inherited + // enumerable properties. + if (iteratesOwnLast) { + forIn(value, function(value, key, object) { + result = !hasOwnProperty.call(object, key); + return false; }); - return result === false || hasOwnProperty.call(value, result); + return result === false; } - return result; - }; + // 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 === false || hasOwnProperty.call(value, result); + } + return result; } + /** + * Checks if a given `value` is an object created by the `Object` constructor. + * + * @private + * @param {Mixed} value The value to check. + * @param {Boolean} [skipArgsCheck=false] Internally used to skip checks for + * `arguments` objects. + * @returns {Boolean} Returns `true` if `value` is a plain object, else `false`. + */ + var isPlainObject = objectTypes.__proto__ != ObjectProto ? isPlainFallback : function(value, skipArgsCheck) { + if (!value) { + return false; + } + var valueOf = value.valueOf, + objProto = typeof valueOf == 'function' && (objProto = valueOf.__proto__) && objProto.__proto__; + + return objProto + ? value == objProto || (value.__proto__ == objProto && (skipArgsCheck || !isArguments(value))) + : isPlainFallback(value); + }; + /** * A shim implementation of `Object.keys` that produces an array of the given * object's own enumerable property names. @@ -1043,10 +1063,9 @@ * @param {Boolean} deep A flag to indicate a deep clone. * @param {Object} [guard] Internally used to allow this method to work with * others like `_.map` without using their callback `index` argument for `deep`. - * @param {Array} [stack=[]] Internally used to keep track of traversed objects - * to avoid circular references. - * @param {Object} thorough Internally used to indicate whether or not to perform - * a more thorough clone of non-object values. + * @param {Object} [data={}] Internally used to track traversed objects to avoid + * circular references and indicate whether to perform a more thorough clone + * of non-object values. * @returns {Mixed} Returns the cloned `value`. * @example * @@ -1067,23 +1086,25 @@ * shallow[0] === stooges[0]; * // => false */ - function clone(value, deep, guard, stack, thorough) { + function clone(value, deep, guard, data) { if (value == null) { return value; } if (guard) { deep = false; } + // init internal data + data || (data = { 'thorough': null }); + // avoid slower checks on primitives - thorough || (thorough = { 'value': null }); - if (thorough.value == null) { + if (data.thorough == null) { // primitives passed from iframes use the primary document's native prototypes - thorough.value = !!(BoolProto.clone || NumberProto.clone || StringProto.clone); + data.thorough = !!(BoolProto.clone || NumberProto.clone || StringProto.clone); } // use custom `clone` method if available var isObj = objectTypes[typeof value]; - if ((isObj || thorough.value) && value.clone && isFunction(value.clone)) { - thorough.value = null; + if ((isObj || data.thorough) && value.clone && isFunction(value.clone)) { + data.thorough = null; return value.clone(deep); } // inspect [[Class]] @@ -1120,31 +1141,33 @@ return ctor(value.source, reFlags.exec(value)); } + var clones = data.clones || (data.clones = []), + sources = data.sources || (data.sources = []), + length = clones.length; + // check for circular references and return corresponding clone - stack || (stack = []); - var length = stack.length; while (length--) { - if (stack[length].source == value) { - return stack[length].value; + if (sources[length] == value) { + return clones[length]; } } // init cloned object - length = value.length; - var result = isArr ? ctor(length) : {}; + var result = isArr ? ctor(length = value.length) : {}; // add current clone and original source value to the stack of traversed objects - stack.push({ 'value': result, 'source': value }); + clones.push(result); + sources.push(value); // recursively populate clone (susceptible to call stack limits) if (isArr) { var index = -1; while (++index < length) { - result[index] = clone(value[index], deep, null, stack, thorough); + result[index] = clone(value[index], deep, null, data); } } else { forOwn(value, function(objValue, key) { - result[key] = clone(objValue, deep, null, stack, thorough); + result[key] = clone(objValue, deep, null, data); }); } return result; @@ -1172,34 +1195,6 @@ 'inLoop': 'if (result[index] == null) ' + extendIteratorOptions.inLoop }); - /** - * 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 `callback` is passed, it will be executed for each property - * in the `object`, dropping the properties `callback` returns truthy for. The - * `callback` is bound to `thisArg` and invoked with 3 arguments; (value, key, object). - * - * @static - * @memberOf _ - * @alias omit - * @category Objects - * @param {Object} object The source object. - * @param {Function|String} callback|[prop1, prop2, ...] The properties to drop - * or the function called per iteration. - * @param {Mixed} [thisArg] The `this` binding for the callback. - * @returns {Object} Returns an object without the dropped properties. - * @example - * - * _.drop({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'userid'); - * // => { 'name': 'moe', 'age': 40 } - * - * _.drop({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) { - * return key.charAt(0) == '_'; - * }); - * // => { 'name': 'moe' } - */ - var drop = createIterator(dropIteratorOptions); - /** * Assigns enumerable properties of the source object(s) to the `destination` * object. Subsequent sources will overwrite propery assignments of previous @@ -1314,6 +1309,25 @@ 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', 'third': 'Curly' }); + * // => { 'Moe': 'first', 'Larry': 'second', 'Curly': 'third' } (order is not guaranteed) + */ + var invert = createIterator({ + 'args': 'object', + 'init': '{}', + 'inLoop': 'result[value] = index' + }); + /** * Checks if `value` is a boolean (`true` or `false`) value. * @@ -1412,10 +1426,9 @@ * @category Objects * @param {Mixed} a The value to compare. * @param {Mixed} b The other value to compare. - * @param {Array} [stack=[]] Internally used to keep track of traversed objects - * to avoid circular references. - * @param {Object} thorough Internally used to indicate whether or not to perform - * a more thorough comparison of non-object values. + * @param {Object} [data={}] Internally used track traversed objects to avoid + * circular references and indicate whether to perform a more thorough comparison + * of non-object values. * @returns {Boolean} Returns `true` if the values are equvalent, else `false`. * @example * @@ -1428,32 +1441,31 @@ * _.isEqual(moe, clone); * // => true */ - function isEqual(a, b, stack, thorough) { + function isEqual(a, b, data) { // a strict comparison is necessary because `null == undefined` if (a == null || b == null) { return a === b; } + // init internal data + data || (data = { 'thorough': null }); + // avoid slower checks on non-objects - thorough || (thorough = { 'value': null }); - if (thorough.value == null) { + if (data.thorough == null) { // primitives passed from iframes use the primary document's native prototypes - thorough.value = !!(BoolProto.isEqual || NumberProto.isEqual || StringProto.isEqual); + data.thorough = !!(BoolProto.isEqual || NumberProto.isEqual || StringProto.isEqual); } - if (objectTypes[typeof a] || objectTypes[typeof b] || thorough.value) { + if (objectTypes[typeof a] || objectTypes[typeof b] || data.thorough) { // unwrap any LoDash wrapped values - if (a._chain) { - a = a._wrapped; - } - if (b._chain) { - b = b._wrapped; - } + a = a.__wrapped__ || a; + b = b.__wrapped__ || b; + // use custom `isEqual` method if available if (a.isEqual && isFunction(a.isEqual)) { - thorough.value = null; + data.thorough = null; return a.isEqual(b); } if (b.isEqual && isFunction(b.isEqual)) { - thorough.value = null; + data.thorough = null; return b.isEqual(a); } } @@ -1502,11 +1514,13 @@ // 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) - stack || (stack = []); - var length = stack.length; + var stackA = data.stackA || (data.stackA = []), + stackB = data.stackB || (data.stackB = []), + length = stackA.length; + while (length--) { - if (stack[length] == a) { - return true; + if (stackA[length] == a) { + return stackB[length] == b; } } @@ -1514,8 +1528,9 @@ result = true, size = 0; - // add `a` to the stack of traversed objects - stack.push(a); + // 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) { @@ -1526,7 +1541,7 @@ if (result) { // deep compare the contents, ignoring non-numeric properties while (size--) { - if (!(result = isEqual(a[size], b[size], stack, thorough))) { + if (!(result = isEqual(a[size], b[size], data))) { break; } } @@ -1550,7 +1565,7 @@ // count the number of properties. size++; // deep compare each property value. - if (!(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack, thorough))) { + if (!(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], data))) { return false; } } @@ -1570,7 +1585,7 @@ while (++index < 7) { prop = shadowed[index]; if (hasOwnProperty.call(a, prop) && - !(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack, thorough))) { + !(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], data))) { return false; } } @@ -1789,8 +1804,8 @@ * @param {Object} [source1, source2, ...] The source objects. * @param {Object} [indicator] Internally used to indicate that the `stack` * argument is an array of traversed objects instead of another source object. - * @param {Array} [stack=[]] Internally used to keep track of traversed objects - * to avoid circular references. + * @param {Object} [data={}] Internally used to track traversed objects to avoid + * circular references. * @returns {Object} Returns the destination object. * @example * @@ -1808,32 +1823,80 @@ * // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] */ var merge = createIterator(extendIteratorOptions, { - 'args': 'object, source, indicator, stack', + 'args': 'object, source, indicator', 'top': - 'var destValue, found, isArr, stackLength, recursive = indicator == isPlainObject;\n' + - 'if (!recursive) stack = [];\n' + + 'var isArr, recursive = indicator == isPlainObject,\n' + + ' data = recursive ? arguments[3] : { values: [], sources: [] };\n' + 'for (var argsIndex = 1, argsLength = recursive ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n' + ' if (iteratee = arguments[argsIndex]) {', 'inLoop': - 'if (value && ((isArr = isArray(value)) || isPlainObject(value))) {\n' + - ' found = false; stackLength = stack.length;\n' + + 'if ((source = value) && ((isArr = isArray(source)) || isPlainObject(source))) {\n' + + ' var found = false, values = data.values, sources = data.sources, stackLength = sources.length;\n' + ' while (stackLength--) {\n' + - ' if (found = stack[stackLength].source == value) break\n' + + ' if (found = sources[stackLength] == source) break\n' + ' }\n' + ' if (found) {\n' + - ' result[index] = stack[stackLength].value\n' + + ' result[index] = values[stackLength]\n' + ' } else {\n' + - ' destValue = (destValue = result[index]) && isArr\n' + - ' ? (isArray(destValue) ? destValue : [])\n' + - ' : (isPlainObject(destValue) ? destValue : {});\n' + - ' stack.push({ value: destValue, source: value });\n' + - ' result[index] = callee(destValue, value, isPlainObject, stack)\n' + + ' values.push(value = (value = result[index]) && isArr\n' + + ' ? (isArray(value) ? value : [])\n' + + ' : (isPlainObject(value) ? value : {})\n' + + ' );\n' + + ' sources.push(source);\n' + + ' result[index] = callee(value, source, isPlainObject, data)\n' + ' }\n' + - '} else if (value != null) {\n' + - ' result[index] = value\n' + + '} else if (source != null) {\n' + + ' result[index] = source\n' + '}' }); + /** + * 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 `callback` 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 3 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 for the callback. + * @returns {Object} Returns an object without the omitted properties. + * @example + * + * _.omit({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'userid'); + * // => { 'name': 'moe', 'age': 40 } + * + * _.omit({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) { + * return key.charAt(0) == '_'; + * }); + * // => { 'name': 'moe' } + */ + var omit = createIterator(omitIteratorOptions); + + /** + * 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, 'curly': 50 }); + * // => [['moe', 30], ['larry', 40], ['curly', 50]] (order is not guaranteed) + */ + var pairs = createIterator({ + 'args': 'object', + 'init':'[]', + 'inLoop': 'result' + (isKeysFast ? '[ownIndex] = ' : '.push') + '([index, value])' + }); + /** * Creates a shallow clone of `object` composed of the specified properties. * Property names may be specified as individual arguments or as arrays of @@ -1859,7 +1922,7 @@ * }); * // => { 'name': 'moe' } */ - var pick = createIterator(dropIteratorOptions, { + var pick = createIterator(omitIteratorOptions, { 'top': 'if (typeof callback != \'function\') {\n' + ' var prop,\n' + @@ -1876,45 +1939,6 @@ 'bottom': '}' }); - /** - * Gets the size of `value` by returning `value.length` if `value` is an - * array, string, or `arguments` object. If `value` is an object, size is - * determined by returning the number of own enumerable properties it has. - * - * @static - * @memberOf _ - * @category Objects - * @param {Array|Object|String} value The value to inspect. - * @returns {Number} Returns `value.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(value) { - if (!value) { - return 0; - } - var className = toString.call(value), - length = value.length; - - // return `value.length` for `arguments` objects, arrays, strings, and DOM - // query collections of libraries like jQuery and MooTools - // http://code.google.com/p/fbug/source/browse/branches/firebug1.9/content/firebug/chrome/reps.js?r=12614#653 - // http://trac.webkit.org/browser/trunk/Source/WebCore/inspector/InjectedScriptSource.js?rev=125186#L609 - if (arrayLikeClasses[className] || (noArgsClass && isArguments(value)) || - (className == objectClass && length > -1 && length === length >>> 0 && isFunction(value.splice))) { - return length; - } - return keys(value).length; - } - /** * Creates an array composed of the own enumerable property values of `object`. * @@ -2321,6 +2345,34 @@ 'inLoop': '!' + filterIteratorOptions.inLoop }); + /** + * 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) { + if (!collection) { + return 0; + } + var length = collection.length; + return length > -1 && length === length >>> 0 ? 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 @@ -2620,7 +2672,7 @@ if (fromIndex) { if (typeof fromIndex == 'number') { - index = (fromIndex < 0 ? Math.max(0, length + fromIndex) : fromIndex) - 1; + index = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) - 1; } else { index = sortedIndex(array, value); return array[index] === value ? index : -1; @@ -2748,7 +2800,7 @@ } var index = array.length; if (fromIndex && typeof fromIndex == 'number') { - index = (fromIndex < 0 ? Math.max(0, index + fromIndex) : Math.min(fromIndex, index - 1)) + 1; + index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1; } while (index--) { if (array[index] === value) { @@ -2864,6 +2916,41 @@ return result; } + /** + * 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 _ + * @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 + * + * _.object(['moe', 'larry', 'curly'], [30, 40, 50]); + * // => { 'moe': 30, 'larry': 40, 'curly': 50 } + */ + function object(keys, values) { + if (!keys) { + return {}; + } + var index = -1, + length = keys.length, + result = {}; + + while (++index < length) { + if (values) { + result[keys[index]] = values[index]; + } else { + result[keys[index][0]] = keys[index][1]; + } + } + return result; + } + /** * Creates an array of numbers (positive and/or negative) progressing from * `start` up to but not including `stop`. This method is a port of Python's @@ -2904,7 +2991,7 @@ // use `Array(length)` so V8 will avoid the slower "dictionary" mode // http://www.youtube.com/watch?v=XAqIpGU8ZZk#t=16m27s var index = -1, - length = Math.max(0, Math.ceil((end - start) / step)), + length = nativeMax(0, Math.ceil((end - start) / step)), result = Array(length); while (++index < length) { @@ -2920,7 +3007,7 @@ * * @static * @memberOf _ - * @alias tail + * @alias drop, tail * @category Arrays * @param {Array} array The array to query. * @param {Number} [n] The number of elements to return. @@ -2963,7 +3050,7 @@ result = Array(length); while (++index < length) { - rand = Math.floor(Math.random() * (index + 1)); + rand = nativeFloor(nativeRandom() * (index + 1)); result[index] = result[rand]; result[rand] = array[index]; } @@ -3186,36 +3273,6 @@ return result; } - /** - * Creates an object composed from an array of `keys` and an array of `values`. - * - * @static - * @memberOf _ - * @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', 'curly'], [30, 40, 50]); - * // => { 'moe': 30, 'larry': 40, 'curly': 50 } - */ - function zipObject(keys, values) { - if (!keys) { - return {}; - } - var index = -1, - length = keys.length, - result = {}; - - values || (values = []); - while (++index < length) { - result[keys[index]] = values[index]; - } - return result; - } - /*--------------------------------------------------------------------------*/ /** @@ -3441,7 +3498,7 @@ function delayed() { timeoutId = null; if (!immediate) { - func.apply(thisArg, args); + result = func.apply(thisArg, args); } } @@ -3627,7 +3684,7 @@ function trailingCall() { lastCalled = new Date; timeoutId = null; - func.apply(thisArg, args); + result = func.apply(thisArg, args); } return function() { @@ -3745,14 +3802,14 @@ var func = lodash[methodName] = object[methodName]; LoDash.prototype[methodName] = function() { - var args = [this._wrapped]; + var args = [this.__wrapped__]; if (arguments.length) { push.apply(args, arguments); } var result = func.apply(lodash, args); - if (this._chain) { + if (this.__chain__) { result = new LoDash(result); - result._chain = true; + result.__chain__ = true; } return result; }; @@ -3776,6 +3833,40 @@ return this; } + /** + * 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. + * If no arguments are passed `_.random` will act as `Math.random`. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Number} min The minimum possible value. + * @param {Number} max The maximum possible value. + * @returns {Number} Returns a random number. + * @example + * + * _.random(0, 5); + * // => a number between 1 and 5 + * + * _.random(5); + * // => also a number between 1 and 5 + * + * _.random(); + * // => an integer between 0 and less than 1 + */ + function random(min, max) { + if (min == null && max == null) { + return nativeRandom(); + } + min = +min || 0; + if (max == null) { + max = min; + min = 0; + } + return min + nativeFloor(nativeRandom() * ((+max || 0) - min + 1)); + } + /** * Resolves the value of `property` on `object`. If `property` is a function * it will be invoked and its result returned, else the property value is @@ -3984,10 +4075,8 @@ try { result = Function('_', 'return ' + text)(lodash); } catch(e) { - // defer syntax errors until the compiled template is executed to allow - // examining the `source` property beforehand and for consistency, - // because other template related errors occur at execution - result = function() { throw e; }; + e.source = text; + throw e; } if (data) { @@ -4012,11 +4101,11 @@ * @param {Mixed} [thisArg] The `this` binding for the callback. * @example * - * _.times(3, function() { genie.grantWish(); }); - * // => calls `genie.grantWish()` 3 times + * _.times(3, function(n) { genie.grantWish(n); }); + * // => calls `genie.grantWish(n)` three times, passing `n` of `0`, `1`, and `2` respectively * - * _.times(3, function() { this.grantWish(); }, genie); - * // => also calls `genie.grantWish()` 3 times + * _.times(3, function(n) { this.grantWish(n); }, genie); + * // => also calls `genie.grantWish(n)` three times */ function times(n, callback, thisArg) { var index = -1; @@ -4095,7 +4184,7 @@ */ function chain(value) { value = new LoDash(value); - value._chain = true; + value.__chain__ = true; return value; } @@ -4139,7 +4228,7 @@ * // => [1, 2, 3] */ function wrapperChain() { - this._chain = true; + this.__chain__ = true; return this; } @@ -4156,7 +4245,7 @@ * // => [1, 2, 3] */ function wrapperValue() { - return this._wrapped; + return this.__wrapped__; } /*--------------------------------------------------------------------------*/ @@ -4168,7 +4257,7 @@ * @memberOf _ * @type String */ - lodash.VERSION = '0.6.1'; + lodash.VERSION = '0.7.0'; // assign static methods lodash.after = after; @@ -4185,7 +4274,6 @@ lodash.defer = defer; lodash.delay = delay; lodash.difference = difference; - lodash.drop = drop; lodash.escape = escape; lodash.every = every; lodash.extend = extend; @@ -4203,6 +4291,7 @@ lodash.indexOf = indexOf; lodash.initial = initial; lodash.intersection = intersection; + lodash.invert = invert; lodash.invoke = invoke; lodash.isArguments = isArguments; lodash.isArray = isArray; @@ -4230,10 +4319,14 @@ lodash.min = min; lodash.mixin = mixin; lodash.noConflict = noConflict; + lodash.object = object; + lodash.omit = omit; lodash.once = once; + lodash.pairs = pairs; lodash.partial = partial; lodash.pick = pick; lodash.pluck = pluck; + lodash.random = random; lodash.range = range; lodash.reduce = reduce; lodash.reduceRight = reduceRight; @@ -4259,13 +4352,13 @@ lodash.without = without; lodash.wrap = wrap; lodash.zip = zip; - lodash.zipObject = zipObject; // assign aliases lodash.all = every; lodash.any = some; lodash.collect = map; lodash.detect = find; + lodash.drop = rest; lodash.each = forEach; lodash.foldl = reduce; lodash.foldr = reduceRight; @@ -4273,7 +4366,6 @@ lodash.include = contains; lodash.inject = reduce; lodash.methods = functions; - lodash.omit = drop; lodash.select = filter; lodash.tail = rest; lodash.take = first; @@ -4301,7 +4393,7 @@ var func = ArrayProto[methodName]; LoDash.prototype[methodName] = function() { - var value = this._wrapped; + var value = this.__wrapped__; func.apply(value, arguments); // avoid array-like object bugs with `Array#shift` and `Array#splice` in @@ -4309,9 +4401,9 @@ if (hasObjectSpliceBug && value.length === 0) { delete value[0]; } - if (this._chain) { + if (this.__chain__) { value = new LoDash(value); - value._chain = true; + value.__chain__ = true; } return value; }; @@ -4322,12 +4414,12 @@ var func = ArrayProto[methodName]; LoDash.prototype[methodName] = function() { - var value = this._wrapped, + var value = this.__wrapped__, result = func.apply(value, arguments); - if (this._chain) { + if (this.__chain__) { result = new LoDash(result); - result._chain = true; + result.__chain__ = true; } return result; }; diff --git a/lib/underscore.deferred.js b/lib/underscore.deferred.js index da2712e..790568e 100644 --- a/lib/underscore.deferred.js +++ b/lib/underscore.deferred.js @@ -10,6 +10,7 @@ hasOwn = OP.hasOwnProperty, toString = OP.toString, forEach = AP.forEach, + indexOf = AP.indexOf, slice = AP.slice; var _each = function( obj, iterator, context ) { @@ -57,12 +58,40 @@ return obj; }; + // $.inArray + var _inArray = function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( indexOf ) { + return indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }; + // And some jQuery specific helpers - var class2type = { "[object Array]": "array", "[object Function]": "function" }; + var class2type = {}; + + // Populate the class2type map + _each("Boolean Number String Function Array Date RegExp Object".split(" "), function(name, i) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); + }); var _type = function( obj ) { - return !obj ? + return obj == null ? String( obj ) : class2type[ toString.call(obj) ] || "object"; }; @@ -72,30 +101,27 @@ // Internal Deferred namespace var _d = {}; - - var flagsCache = {}; - // Convert String-formatted flags into Object-formatted ones and store in cache - function createFlags( flags ) { - var object = flagsCache[ flags ] = {}, - i, length; - flags = flags.split( /\s+/ ); - for ( i = 0, length = flags.length; i < length; i++ ) { - object[ flags[i] ] = true; - } - return object; + // String to Object options format cache + var optionsCache = {}; + + // Convert String-formatted options into Object-formatted ones and store in cache + function createOptions( options ) { + var object = optionsCache[ options ] = {}; + _each( options.split( /\s+/ ), function( flag ) { + object[ flag ] = true; + }); + return object; } - _d.Callbacks = function( flags ) { + _d.Callbacks = function( options ) { - // Convert flags from String-formatted to Object-formatted + // Convert options from String-formatted to Object-formatted if needed // (we check in cache first) - flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + _extend( {}, options ); - var // Actual callback list - list = [], - // Stack of fire calls for repeatable lists - stack = [], - // Last fire value (for non-forgettable lists) + var // Last fire value (for non-forgettable lists) memory, // Flag to know if list was already fired fired, @@ -107,53 +133,34 @@ firingLength, // Index of currently firing callback (modified by remove if needed) firingIndex, - // Add one or several callbacks to the list - add = function( args ) { - var i, - length, - elem, - type, - actual; - for ( i = 0, length = args.length; i < length; i++ ) { - elem = args[ i ]; - type = _type( elem ); - if ( type === "array" ) { - // Inspect recursively - add( elem ); - } else if ( type === "function" ) { - // Add if not in unique mode and callback is not in - if ( !flags.unique || !self.has( elem ) ) { - list.push( elem ); - } - } - } - }, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], // Fire callbacks - fire = function( context, args ) { - args = args || []; - memory = !flags.memory || [ context, args ]; + fire = function( data ) { + memory = options.memory && data; fired = true; - firing = true; firingIndex = firingStart || 0; firingStart = 0; firingLength = list.length; + firing = true; for ( ; list && firingIndex < firingLength; firingIndex++ ) { - if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { - memory = true; // Mark as halted + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add break; } } firing = false; if ( list ) { - if ( !flags.once ) { - if ( stack && stack.length ) { - memory = stack.shift(); - self.fireWith( memory[ 0 ], memory[ 1 ] ); + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); } - } else if ( memory === true ) { - self.disable(); - } else { + } else if ( memory ) { list = []; + } else { + self.disable(); } } }, @@ -162,18 +169,28 @@ // Add a callback or a collection of callbacks to the list add: function() { if ( list ) { - var length = list.length; - add( arguments ); + // First, we save the current length + var start = list.length; + (function add( args ) { + _each( args, function( arg ) { + var type = _type( arg ); + if ( type === "function" && ( !options.unique || !self.has( arg ) ) ) { + list.push( arg ); + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); // Do we need to add the callbacks to the // current firing batch? if ( firing ) { firingLength = list.length; // With memory, if we're not firing then - // we should call right away, unless previous - // firing was halted (stopOnFalse) - } else if ( memory && memory !== true ) { - firingStart = length; - fire( memory[ 0 ], memory[ 1 ] ); + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); } } return this; @@ -181,46 +198,27 @@ // Remove a callback from the list remove: function() { if ( list ) { - var args = arguments, - argIndex = 0, - argLength = args.length; - for ( ; argIndex < argLength ; argIndex++ ) { - for ( var i = 0; i < list.length; i++ ) { - if ( args[ argIndex ] === list[ i ] ) { - // Handle firingIndex and firingLength - if ( firing ) { - if ( i <= firingLength ) { - firingLength--; - if ( i <= firingIndex ) { - firingIndex--; - } - } + _each( arguments, function( arg ) { + var index; + while( ( index = _inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; } - // Remove the element - list.splice( i--, 1 ); - // If we have some unicity property then - // we only need to do this once - if ( flags.unique ) { - break; + if ( index <= firingIndex ) { + firingIndex--; } } } - } + }); } return this; }, // Control if a given callback is in the list has: function( fn ) { - if ( list ) { - var i = 0, - length = list.length; - for ( ; i < length; i++ ) { - if ( fn === list[ i ] ) { - return true; - } - } - } - return false; + return _inArray( fn, list ) > -1; }, // Remove all callbacks from the list empty: function() { @@ -239,7 +237,7 @@ // Lock the list in its current state lock: function() { stack = undefined; - if ( !memory || memory === true ) { + if ( !memory ) { self.disable(); } return this; @@ -250,13 +248,13 @@ }, // Call all callbacks with the given context and arguments fireWith: function( context, args ) { - if ( stack ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( list && ( !fired || stack ) ) { if ( firing ) { - if ( !flags.once ) { - stack.push( [ context, args ] ); - } - } else if ( !( flags.once && memory ) ) { - fire( context, args ); + stack.push( args ); + } else { + fire( args ); } } return this; @@ -276,140 +274,144 @@ }; _d.Deferred = function( func ) { - var doneList = _d.Callbacks( "once memory" ), - failList = _d.Callbacks( "once memory" ), - progressList = _d.Callbacks( "memory" ), - state = "pending", - lists = { - resolve: doneList, - reject: failList, - notify: progressList + + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", _d.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", _d.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", _d.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; }, - promise = { - done: doneList.add, - fail: failList.add, - progress: progressList.add, - - state: function() { - return state; - }, - - // Deprecated - isResolved: doneList.fired, - isRejected: failList.fired, - - then: function( doneCallbacks, failCallbacks, progressCallbacks ) { - deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); - return this; - }, - always: function() { - deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); - return this; - }, - pipe: function( fnDone, fnFail, fnProgress ) { - return _d.Deferred(function( newDefer ) { - _each( { - done: [ fnDone, "resolve" ], - fail: [ fnFail, "reject" ], - progress: [ fnProgress, "notify" ] - }, function( data, handler ) { - var fn = data[ 0 ], - action = data[ 1 ], - returned; - if ( _isFunction( fn ) ) { - deferred[ handler ](function() { - returned = fn.apply( this, arguments ); - if ( returned && _isFunction( returned.promise ) ) { - returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); - } else { - newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); - } - }); - } else { - deferred[ handler ]( newDefer[ action ] ); - } - }); - }).promise(); - }, - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - if ( !obj ) { - obj = promise; - } else { - for ( var key in promise ) { - obj[ key ] = promise[ key ]; - } - } - return obj; - } + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; }, - deferred = promise.promise({}), - key; - - for ( key in lists ) { - deferred[ key ] = lists[ key ].fire; - deferred[ key + "With" ] = lists[ key ].fireWith; + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return _d.Deferred(function( newDefer ) { + _each( tuples, function( tuple, i ) { + var action = tuple[ 0 ], + fn = fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ]( _isFunction( fn ) ? + function() { + var returned = fn.apply( this, arguments ); + if ( returned && _isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); + } + } : + newDefer[ action ] + ); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return typeof obj === "object" ? _extend( obj, promise ) : promise; } + }, + deferred = {}; - // Handle state - deferred.done( function() { - state = "resolved"; - }, failList.disable, progressList.lock ).fail( function() { - state = "rejected"; - }, doneList.disable, progressList.lock ); + // Keep pipe for back-compat + promise.pipe = promise.then; - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } + // Add list-specific methods + _each( tuples, function( tuple, i ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] = list.fire + deferred[ tuple[0] ] = list.fire; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } - // All done! - return deferred; - }; + // All done! + return deferred; + }; // Deferred helper - _d.when = function( firstParam ) { - var args = slice.call( arguments, 0 ), - i = 0, - length = args.length, - pValues = new Array( length ), - count = length, - pCount = length, - deferred = length <= 1 && firstParam && _isFunction( firstParam.promise ) ? - firstParam : - _d.Deferred(), - promise = deferred.promise(); - function resolveFunc( i ) { + _d.when = function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && _isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : _d.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { return function( value ) { - args[ i ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value; - if ( !( --count ) ) { - deferred.resolveWith( deferred, args ); + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); } }; - } - function progressFunc( i ) { - return function( value ) { - pValues[ i ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value; - deferred.notifyWith( promise, pValues ); - }; - } - if ( length > 1 ) { - for ( ; i < length; i++ ) { - if ( args[ i ] && args[ i ].promise && _isFunction( args[ i ].promise ) ) { - args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); - } else { - --count; - } - } - if ( !count ) { - deferred.resolveWith( deferred, args ); + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && _isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; } - } else if ( deferred !== firstParam ) { - deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); } - return promise; - }; + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + }; // Try exporting as a Common.js Module if ( typeof module !== "undefined" && module.exports ) { diff --git a/package.json b/package.json index 3e04f5a..ee0622d 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,9 @@ }, "dependencies": { - "lodash": "0.6.1", + "lodash": "0.7.0", "moment": "1.7.0", - "underscore.deferred": "0.1.3", + "underscore.deferred": "0.1.4", "request": "2.9.153" } } From 44d6aa4d14b3ac8d41272db37af201ac8dec0c37 Mon Sep 17 00:00:00 2001 From: Irene Ros Date: Wed, 19 Sep 2012 14:57:25 -0400 Subject: [PATCH 10/12] changing node version dependency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ee0622d..2d9db26 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "keywords" : [ "data", "dataset" ], "engines": { - "node": "~0.6" + "node": ">= 0.6.0" }, "dependencies": { From 1cf177be491192c7c9f931e05add50ce925faa83 Mon Sep 17 00:00:00 2001 From: Alex Graul Date: Sun, 23 Sep 2012 10:25:50 +0100 Subject: [PATCH 11/12] moved all Dataset objects under the Miso.Dataset namespace --- src/builder.js | 12 +-- src/constructor.js | 46 +++++++++ src/dataset.js | 83 ++++----------- src/derived.js | 36 +++---- src/importer.js | 7 +- src/importers/google_spreadsheet.js | 8 +- src/importers/local.js | 7 +- src/importers/polling.js | 10 +- src/importers/remote.js | 20 ++-- src/parser.js | 6 +- src/parsers/delimited.js | 6 +- src/parsers/google_spreadsheet.js | 6 +- src/parsers/object.js | 7 +- src/parsers/strict.js | 7 +- src/product.js | 29 +++--- src/sync.js | 20 ++-- src/types.js | 10 +- src/view.js | 50 ++++----- test/index.html | 2 +- test/unit/bugs.js | 49 ++++----- test/unit/core.js | 35 ++++--- test/unit/derived.js | 1 + test/unit/helpers.js | 3 +- test/unit/importers.js | 85 +++++++-------- test/unit/products.js | 29 +++--- test/unit/types.js | 154 ++++++++++++++-------------- test/unit/views.js | 36 +++---- 27 files changed, 389 insertions(+), 375 deletions(-) create mode 100644 src/constructor.js diff --git a/src/builder.js b/src/builder.js index 9d68f4c..9208990 100644 --- a/src/builder.js +++ b/src/builder.js @@ -1,12 +1,12 @@ (function(global, _) { - var Miso = global.Miso || {}; + var Dataset = global.Miso.Dataset; /** * This is a generic collection of dataset-building utilities * that are used by Miso.Dataset and Miso.DataView. */ - Miso.Builder = { + Dataset.Builder = { /** * Detects the type of a column based on some input data. @@ -22,7 +22,7 @@ // and then squashing it to create a unique subset. var type = _.inject(data.slice(0, 5), function(memo, value) { - var t = Miso.typeOf(value); + var t = Dataset.typeOf(value); if (value !== "" && memo.indexOf(t) === -1 && !_.isNull(value)) { memo.push(t); @@ -58,7 +58,7 @@ column.force = true; return; } else { - Miso.Builder.detectColumnType(column, data); + Dataset.Builder.detectColumnType(column, data); } }, this); @@ -72,7 +72,7 @@ */ cacheRows : function(dataset) { - Miso.Builder.clearRowCache(dataset); + Dataset.Builder.clearRowCache(dataset); // cache the row id positions in both directions. // iterate over the _id column and grab the row ids @@ -128,4 +128,4 @@ }; } -}(this, _)); \ No newline at end of file +}(this, _)); diff --git a/src/constructor.js b/src/constructor.js new file mode 100644 index 0000000..f46e98a --- /dev/null +++ b/src/constructor.js @@ -0,0 +1,46 @@ +(function(global, _, moment) { + +/** + * Instantiates a new dataset. + * Parameters: + * options - optional parameters. + * data : "Object - an actual javascript object that already contains the data", + * url : "String - url to fetch data from", + * sync : Set to true to be able to bind to dataset changes. False by default. + * jsonp : "boolean - true if this is a jsonp request", + * delimiter : "String - a delimiter string that is used in a tabular datafile", + * strict : "Whether to expect the json in our format or whether to interpret as raw array of objects, default false", + * extract : "function to apply to JSON before internal interpretation, optional" + * ready : the callback function to act on once the data is fetched. Isn't reuired for local imports + * but is required for remote url fetching. + * columns: A way to manually override column type detection. Expects an array of + * objects of the following structure: + * { name : 'columnname', type: 'columntype', + * ... (additional params required for type here.) } + * comparator : function (optional) - takes two rows and returns 1, 0, or -1 if row1 is + * before, equal or after row2. + * deferred : by default we use underscore.deferred, but if you want to pass your own (like jquery's) just + * pass it here. + * importer : The classname of any importer (passes through auto detection based on parameters. + * For example: Miso.Importers.Polling. + * parser : The classname of any parser (passes through auto detection based on parameters. + * For example: Miso.Parsers.Delimited. + * resetOnFetch : set to true if any subsequent fetches after first one should overwrite the + * current data. + * uniqueAgainst : Set to a column name to check for duplication on subsequent fetches. + * interval : Polling interval. Set to any value in milliseconds to enable polling on a url. + } + */ + global.Miso.Dataset = function(options) { + this.length = 0; + + this._columns = []; + this._columnPositionByName = {}; + this._computedColumns = []; + + if (typeof options !== "undefined") { + options = options || {}; + this._initialize(options); + } + }; +}(this, _, moment)); diff --git a/src/dataset.js b/src/dataset.js index d6a5795..6188a50 100644 --- a/src/dataset.js +++ b/src/dataset.js @@ -7,57 +7,14 @@ Version 0.0.1.2 (function(global, _, moment) { - var Miso = global.Miso; - - /** - * Instantiates a new dataset. - * Parameters: - * options - optional parameters. - * data : "Object - an actual javascript object that already contains the data", - * url : "String - url to fetch data from", - * sync : Set to true to be able to bind to dataset changes. False by default. - * jsonp : "boolean - true if this is a jsonp request", - * delimiter : "String - a delimiter string that is used in a tabular datafile", - * strict : "Whether to expect the json in our format or whether to interpret as raw array of objects, default false", - * extract : "function to apply to JSON before internal interpretation, optional" - * ready : the callback function to act on once the data is fetched. Isn't reuired for local imports - * but is required for remote url fetching. - * columns: A way to manually override column type detection. Expects an array of - * objects of the following structure: - * { name : 'columnname', type: 'columntype', - * ... (additional params required for type here.) } - * comparator : function (optional) - takes two rows and returns 1, 0, or -1 if row1 is - * before, equal or after row2. - * deferred : by default we use underscore.deferred, but if you want to pass your own (like jquery's) just - * pass it here. - * importer : The classname of any importer (passes through auto detection based on parameters. - * For example: Miso.Importers.Polling. - * parser : The classname of any parser (passes through auto detection based on parameters. - * For example: Miso.Parsers.Delimited. - * resetOnFetch : set to true if any subsequent fetches after first one should overwrite the - * current data. - * uniqueAgainst : Set to a column name to check for duplication on subsequent fetches. - * interval : Polling interval. Set to any value in milliseconds to enable polling on a url. - } - */ - Miso.Dataset = function(options) { - this.length = 0; - - this._columns = []; - this._columnPositionByName = {}; - this._computedColumns = []; - - if (typeof options !== "undefined") { - options = options || {}; - this._initialize(options); - } - }; + var Dataset = global.Miso.Dataset; // take on miso dataview's prototype - Miso.Dataset.prototype = new Miso.DataView(); + Dataset.prototype = new Dataset.DataView(); + console.log(Dataset.prototype); // add dataset methods to dataview. - _.extend(Miso.Dataset.prototype, { + _.extend(Dataset.prototype, { /** * @private @@ -69,7 +26,7 @@ Version 0.0.1.2 // is this a syncable dataset? if so, pull // required methods and mark this as a syncable dataset. if (options.sync === true) { - _.extend(this, Miso.Events); + _.extend(this, Dataset.Events); this.syncable = true; } @@ -78,14 +35,14 @@ Version 0.0.1.2 this.importer = options.importer || null; // default parser is object parser, unless otherwise specified. - this.parser = options.parser || Miso.Parsers.Obj; + this.parser = options.parser || Dataset.Parsers.Obj; // figure out out if we need another parser. if (_.isUndefined(options.parser)) { if (options.strict) { - this.parser = Miso.Parsers.Strict; + this.parser = Dataset.Parsers.Strict; } else if (options.delimiter) { - this.parser = Miso.Parsers.Delimited; + this.parser = Dataset.Parsers.Delimited; } } @@ -94,21 +51,21 @@ Version 0.0.1.2 if (options.url) { if (!options.interval) { - this.importer = Miso.Importers.Remote; + this.importer = Dataset.Importers.Remote; } else { - this.importer = Miso.Importers.Polling; + this.importer = Dataset.Importers.Polling; this.interval = options.interval; } } else { - this.importer = Miso.Importers.Local; + this.importer = Dataset.Importers.Local; } } // initialize importer and parser this.parser = new this.parser(options); - if (this.parser instanceof Miso.Parsers.Delimited) { + if (this.parser instanceof Dataset.Parsers.Delimited) { options.dataType = "text"; } @@ -253,7 +210,7 @@ Version 0.0.1.2 toRemove = []; _.each(data[uniqName], function(key, dataIndex) { - var rowIndex = uniqCol.data.indexOf( Miso.types[uniqCol.type].coerce(key) ); + var rowIndex = uniqCol.data.indexOf( Dataset.types[uniqCol.type].coerce(key) ); var row = {}; _.each(data, function(col, name) { @@ -312,7 +269,7 @@ Version 0.0.1.2 ); // detect column types, add all rows blindly and cache them. - Miso.Builder.detectColumnTypes(this, parsed.data); + Dataset.Builder.detectColumnTypes(this, parsed.data); this._applications.blind.call( this, parsed.data ); this.fetched = true; @@ -341,7 +298,7 @@ Version 0.0.1.2 this._applications.blind.call( this, parsed.data ); } - Miso.Builder.cacheRows(this); + Dataset.Builder.cacheRows(this); }, /** @@ -368,11 +325,11 @@ Version 0.0.1.2 } else { // check that this is a known type. - if (typeof Miso.types[type] === "undefined") { + if (typeof Dataset.types[type] === "undefined") { throw "The type " + type + " doesn't exist"; } - var column = new Miso.Column({ + var column = new Dataset.Column({ name : name, type : type, func : _.bind(func, this) @@ -406,7 +363,7 @@ Version 0.0.1.2 return false; } - column = new Miso.Column( column ); + column = new Dataset.Column( column ); this._columns.push( column ); this._columnPositionByName[column.name] = this._columns.length - 1; @@ -566,7 +523,7 @@ Version 0.0.1.2 } // test if the value passes the type test - var Type = Miso.types[c.type]; + var Type = Dataset.types[c.type]; if (Type) { if (Type.test(props[c.name], c)) { @@ -580,7 +537,7 @@ Version 0.0.1.2 props[c.name] = Type.coerce(props[c.name], c); } else { throw("incorrect value '" + props[c.name] + - "' of type " + Miso.typeOf(props[c.name], c) + + "' of type " + Dataset.typeOf(props[c.name], c) + " passed to column '" + c.name + "' with type " + c.type); } } diff --git a/src/derived.js b/src/derived.js index 9b02835..3c66e7c 100644 --- a/src/derived.js +++ b/src/derived.js @@ -1,6 +1,6 @@ (function(global, _) { - var Miso = global.Miso || (global.Miso = {}); + var Dataset = global.Miso.Dataset; /** * A Miso.Derived dataset is a regular dataset that has been derived @@ -15,10 +15,10 @@ * a derived dataset instance */ - Miso.Derived = function(options) { + Dataset.Derived = function(options) { options = options || {}; - Miso.Dataset.call(this); + Dataset.call(this); // save parent dataset reference this.parent = options.parent; @@ -34,17 +34,17 @@ }); if (this.parent.syncable) { - _.extend(this, Miso.Events); + _.extend(this, Dataset.Events); this.syncable = true; this.parent.bind("change", this._sync, this); } }; // take in dataset's prototype. - Miso.Derived.prototype = new Miso.Dataset(); + Dataset.Derived.prototype = new Dataset(); // inherit all of dataset's methods. - _.extend(Miso.Derived.prototype, { + _.extend(Dataset.Derived.prototype, { _sync : function(event) { // recompute the function on an event. // TODO: would be nice to be more clever about this at some point. @@ -55,7 +55,7 @@ // add derived methods to dataview (and thus dataset & derived) - _.extend(Miso.DataView.prototype, { + _.extend(Dataset.DataView.prototype, { /** * moving average @@ -71,7 +71,7 @@ options = options || {}; - var d = new Miso.Derived({ + var d = new Dataset.Derived({ parent : this, method : options.method || _.mean, size : size, @@ -86,7 +86,7 @@ }, this); // save column positions on new dataset. - Miso.Builder.cacheColumns(d); + Dataset.Builder.cacheColumns(d); // apply with the arguments columns, size, method var computeMovingAverage = function() { @@ -120,7 +120,7 @@ oidcol.data.push(this.parent.column("_id").data.slice(i, i+size)); } - Miso.Builder.cacheRows(this); + Dataset.Builder.cacheRows(this); return this; }; @@ -136,7 +136,7 @@ countBy : function(byColumn, options) { options = options || {}; - var d = new Miso.Derived({ + var d = new Dataset.Derived({ parent : this, method : _.sum, args : arguments @@ -150,7 +150,7 @@ }); d.addColumn({ name : 'count', type : 'number' }); d.addColumn({ name : '_oids', type : 'mixed' }); - Miso.Builder.cacheColumns(d); + Dataset.Builder.cacheColumns(d); var names = d._column(byColumn).data, values = d._column('count').data, @@ -160,7 +160,7 @@ function findIndex(names, datum, type) { var i; for(i = 0; i < names.length; i++) { - if (Miso.types[type].compare(names[i], datum) === 0) { + if (Dataset.types[type].compare(names[i], datum) === 0) { return i; } } @@ -180,7 +180,7 @@ } }); - Miso.Builder.cacheRows(d); + Dataset.Builder.cacheRows(d); return d; }, @@ -201,7 +201,7 @@ options = options || {}; - var d = new Miso.Derived({ + var d = new Dataset.Derived({ // save a reference to parent dataset parent : this, @@ -229,14 +229,14 @@ }, d); // save column positions on new dataset. - Miso.Builder.cacheColumns(d); + Dataset.Builder.cacheColumns(d); // will get called with all the arguments passed to this // host function var computeGroupBy = function() { // clear row cache if it exists - Miso.Builder.clearRowCache(this); + Dataset.Builder.clearRowCache(this); // a cache of values var categoryPositions = {}, @@ -311,7 +311,7 @@ }, this); - Miso.Builder.cacheRows(this); + Dataset.Builder.cacheRows(this); return this; }; diff --git a/src/importer.js b/src/importer.js index 1c75ddf..f5c1677 100644 --- a/src/importer.js +++ b/src/importer.js @@ -1,7 +1,8 @@ (function(global, _) { - var Miso = (global.Miso || (global.Miso = {})); - Miso.Importers = function(data, options) {}; + var Dataset = global.Miso.Dataset; + + Dataset.Importers = function(data, options) {}; /** * Simple base extract method, passing data through @@ -10,7 +11,7 @@ * a dataset, overwrite this method to return the * actual data object. */ - Miso.Importers.prototype.extract = function(data) { + Dataset.Importers.prototype.extract = function(data) { data = _.clone(data); return data; }; diff --git a/src/importers/google_spreadsheet.js b/src/importers/google_spreadsheet.js index afef60e..8c888ea 100644 --- a/src/importers/google_spreadsheet.js +++ b/src/importers/google_spreadsheet.js @@ -1,6 +1,6 @@ (function(global, _) { - var Miso = (global.Miso || (global.Miso = {})); + var Dataset = global.Miso.Dataset; /** * Instantiates a new google spreadsheet importer. @@ -14,7 +14,7 @@ * url - a more complex url (that may include filtering.) In this case * make sure it's returning the feed json data. */ - Miso.Importers.GoogleSpreadsheet = function(options) { + Dataset.Importers.GoogleSpreadsheet = function(options) { options = options || {}; if (options.url) { @@ -65,6 +65,6 @@ return this; }; - _.extend(Miso.Importers.GoogleSpreadsheet.prototype, Miso.Importers.Remote.prototype); + _.extend(Dataset.Importers.GoogleSpreadsheet.prototype, Dataset.Importers.Remote.prototype); -}(this, _)); \ No newline at end of file +}(this, _)); diff --git a/src/importers/local.js b/src/importers/local.js index 3e3f978..7ddf5b9 100644 --- a/src/importers/local.js +++ b/src/importers/local.js @@ -1,18 +1,19 @@ (function(global, _) { - var Miso = (global.Miso || (global.Miso = {})); + + var Dataset = global.Miso.Dataset; /** * Local data importer is responsible for just using * a data object and passing it appropriately. */ - Miso.Importers.Local = function(options) { + Dataset.Importers.Local = function(options) { options = options || {}; this.data = options.data || null; this.extract = options.extract || this.extract; }; - _.extend(Miso.Importers.Local.prototype, Miso.Importers.prototype, { + _.extend(Dataset.Importers.Local.prototype, Dataset.Importers.prototype, { fetch : function(options) { var data = options.data ? options.data : this.data; options.success( this.extract(data) ); diff --git a/src/importers/polling.js b/src/importers/polling.js index fa05664..ce40523 100644 --- a/src/importers/polling.js +++ b/src/importers/polling.js @@ -1,6 +1,6 @@ (function(global,_){ - var Miso = (global.Miso || (global.Miso = {})); + var Dataset = global.Miso.Dataset; /** * A remote polling importer that queries a url once every 1000 @@ -9,15 +9,15 @@ * interval - poll every N milliseconds. Default is 1000. * extract - a method to pass raw data through before handing back to parser. */ - Miso.Importers.Polling = function(options) { + Dataset.Importers.Polling = function(options) { options = options || {}; this.interval = options.interval || 1000; this._def = null; - Miso.Importers.Remote.apply(this, [options]); + Dataset.Importers.Remote.apply(this, [options]); }; - _.extend(Miso.Importers.Polling.prototype, Miso.Importers.Remote.prototype, { + _.extend(Dataset.Importers.Polling.prototype, Dataset.Importers.Remote.prototype, { fetch : function(options) { if (this._def === null) { @@ -52,7 +52,7 @@ importer._def = _.Deferred(); }); - Miso.Xhr(_.extend(this.params, { + Dataset.Xhr(_.extend(this.params, { success : this.success_callback, error : this.error_callback })); diff --git a/src/importers/remote.js b/src/importers/remote.js index 868f705..b1be5c2 100644 --- a/src/importers/remote.js +++ b/src/importers/remote.js @@ -1,5 +1,7 @@ (function(global, _) { - var Miso = (global.Miso || (global.Miso = {})); + + var Dataset = global.Miso.Dataset; + /** * A remote importer is responsible for fetching data from a url. @@ -10,7 +12,7 @@ * dataType - ajax datatype * jsonp - true if it's a jsonp request, false otherwise. */ - Miso.Importers.Remote = function(options) { + Dataset.Importers.Remote = function(options) { options = options || {}; this._url = options.url; @@ -25,7 +27,7 @@ }; }; - _.extend(Miso.Importers.Remote.prototype, Miso.Importers.prototype, { + _.extend(Dataset.Importers.Remote.prototype, Dataset.Importers.prototype, { fetch : function(options) { // call the original fetch method of object parsing. @@ -42,7 +44,7 @@ } // make ajax call to fetch remote url. - Miso.Xhr(_.extend(this.params, { + Dataset.Xhr(_.extend(this.params, { success : this.callback ? this.callback : callback, error : options.error })); @@ -62,7 +64,7 @@ } }, rparams = /\?/; - Miso.Xhr = function(options) { + Dataset.Xhr = function(options) { // json|jsonp etc. options.dataType = options.dataType && options.dataType.toLowerCase() || null; @@ -72,7 +74,7 @@ if (options.dataType && (options.dataType === "jsonp" || options.dataType === "script" )) { - Miso.Xhr.getJSONP( + Dataset.Xhr.getJSONP( url, options.success, options.dataType === "script", @@ -101,11 +103,11 @@ settings.ajax.open(settings.type, settings.url, settings.async); settings.ajax.send(settings.data || null); - return Miso.Xhr.httpData(settings); + return Dataset.Xhr.httpData(settings); } }; - Miso.Xhr.getJSONP = function(url, success, isScript, error, callback) { + Dataset.Xhr.getJSONP = function(url, success, isScript, error, callback) { // If this is a script request, ensure that we do not // call something that has already been loaded if (isScript) { @@ -215,7 +217,7 @@ return; }; - Miso.Xhr.httpData = function(settings) { + Dataset.Xhr.httpData = function(settings) { var data, json = null, handleResponse; handleResponse = function () { diff --git a/src/parser.js b/src/parser.js index 0bbbe3e..0dd616f 100644 --- a/src/parser.js +++ b/src/parser.js @@ -1,15 +1,15 @@ (function(global, _) { - var Miso = (global.Miso || (global.Miso = {})); + var Dataset = global.Miso.Dataset; /** * Base Miso.Parser class. */ - Miso.Parsers = function( options ) { + Dataset.Parsers = function( options ) { this.options = options || {}; }; - _.extend(Miso.Parsers.prototype, { + _.extend(Dataset.Parsers.prototype, { //this is the main function for the parser, //it must return an object with the columns names diff --git a/src/parsers/delimited.js b/src/parsers/delimited.js index f821d9a..112bf45 100644 --- a/src/parsers/delimited.js +++ b/src/parsers/delimited.js @@ -1,6 +1,6 @@ (function(global, _) { - var Miso = (global.Miso || (global.Miso = {})); + var Dataset = global.Miso.Dataset; /** * Delimited data parser. @@ -9,7 +9,7 @@ * options * delimiter : "," */ - Miso.Parsers.Delimited = function(options) { + Dataset.Parsers.Delimited = function(options) { options = options || {}; this.delimiter = options.delimiter || ","; @@ -40,7 +40,7 @@ }; } - _.extend(Miso.Parsers.Delimited.prototype, Miso.Parsers.prototype, { + _.extend(Dataset.Parsers.Delimited.prototype, Dataset.Parsers.prototype, { parse : function(data) { var columns = [], diff --git a/src/parsers/google_spreadsheet.js b/src/parsers/google_spreadsheet.js index 2ee00b2..61a354b 100644 --- a/src/parsers/google_spreadsheet.js +++ b/src/parsers/google_spreadsheet.js @@ -3,18 +3,18 @@ (function(global, _) { - var Miso = (global.Miso || (global.Miso = {})); + var Dataset = global.Miso.Dataset; /** * Google Spreadsheet Parser. * This is utilizing the format that can be obtained using this: * http://code.google.com/apis/gdata/samples/spreadsheet_sample.html * Used in conjunction with the Google Spreadsheet Importer. */ - Miso.Parsers.GoogleSpreadsheet = function(options) { + Dataset.Parsers.GoogleSpreadsheet = function(options) { this.fast = options.fast || false; }; - _.extend(Miso.Parsers.GoogleSpreadsheet.prototype, Miso.Parsers.prototype, { + _.extend(Dataset.Parsers.GoogleSpreadsheet.prototype, Dataset.Parsers.prototype, { parse : function(data) { var columns = [], diff --git a/src/parsers/object.js b/src/parsers/object.js index e5216a3..e57a85d 100644 --- a/src/parsers/object.js +++ b/src/parsers/object.js @@ -1,14 +1,15 @@ (function(global, _) { - var Miso = (global.Miso || (global.Miso = {})); + + var Dataset = global.Miso.Dataset; /** * Object parser * Converts an array of objects to strict format. * Each object is a flat json object of properties. */ - Miso.Parsers.Obj = Miso.Parsers; + Dataset.Parsers.Obj = Dataset.Parsers; - _.extend(Miso.Parsers.Obj.prototype, Miso.Parsers.prototype, { + _.extend(Dataset.Parsers.Obj.prototype, Dataset.Parsers.prototype, { parse : function( data ) { var columns = _.keys(data[0]), diff --git a/src/parsers/strict.js b/src/parsers/strict.js index 422a0bc..fcafc30 100644 --- a/src/parsers/strict.js +++ b/src/parsers/strict.js @@ -1,5 +1,6 @@ (function(global, _) { - var Miso = (global.Miso || (global.Miso = {})); + + var Dataset = global.Miso.Dataset; /** * Strict format parser. @@ -12,11 +13,11 @@ * } * } */ - Miso.Parsers.Strict = function( options ) { + Dataset.Parsers.Strict = function( options ) { this.options = options || {}; }; - _.extend( Miso.Parsers.Strict.prototype, Miso.Parsers.prototype, { + _.extend( Dataset.Parsers.Strict.prototype, Dataset.Parsers.prototype, { parse : function( data ) { var columnData = {}, columnNames = []; diff --git a/src/product.js b/src/product.js index f75fda5..2d631ca 100644 --- a/src/product.js +++ b/src/product.js @@ -1,7 +1,6 @@ (function(global, _) { - // shorthand - var Miso = global.Miso; + var Dataset = global.Miso.Dataset; /** * A Miso.Product is a single computed value that can be obtained @@ -12,7 +11,7 @@ * func - the function that derives the computation. * columns - the columns from which the function derives the computation */ - Miso.Product = (Miso.Product || function(options) { + Dataset.Product = function(options) { options = options || {}; // save column name. This will be necessary later @@ -35,9 +34,9 @@ this.func({ silent : true }); return this; - }); + }; - _.extend(Miso.Product.prototype, Miso.Events, { + _.extend(Dataset.Product.prototype, Dataset.Events, { /** * return the raw value of the product @@ -72,7 +71,7 @@ } }); - Miso.Product.define = function(func) { + Dataset.Product.define = function(func) { return function(columns, options) { options = options || {}; var columnObjects = this._findColumns(columns); @@ -83,12 +82,12 @@ //define wrapper function to handle coercion var producer = function() { var val = func.call(_self, columnObjects, options); - return Miso.types[options.type].coerce(val, options.typeOptions); + return Dataset.types[options.type].coerce(val, options.typeOptions); }; if (this.syncable) { //create product object to pass back for syncable datasets/views - var prod = new Miso.Product({ + var prod = new Dataset.Product({ columns : columnObjects, func : function(options) { options = options || {}; @@ -113,7 +112,7 @@ }; - _.extend(Miso.DataView.prototype, { + _.extend(Dataset.DataView.prototype, { // finds the column objects that match the single/multiple // input columns. Helper method. @@ -144,9 +143,9 @@ * options * silent - set to tue to prevent event propagation */ - sum : Miso.Product.define( function(columns, options) { + sum : Dataset.Product.define( function(columns, options) { _.each(columns, function(col) { - if (col.type === Miso.types.time.name) { + if (col.type === Dataset.types.time.name) { throw new Error("Can't sum up time"); } }); @@ -159,7 +158,7 @@ * Parameters: * column - string or array of column names on which the value is calculated */ - max : Miso.Product.define( function(columns, options) { + max : Dataset.Product.define( function(columns, options) { return _.max(_.map(columns, function(c) { return c._max(); })); @@ -172,7 +171,7 @@ * Paramaters: * columns - string or array of column names on which the value is calculated */ - min : Miso.Product.define( function(columns, options) { + min : Dataset.Product.define( function(columns, options) { return _.min(_.map(columns, function(c) { return c._min(); })); @@ -184,7 +183,7 @@ * Parameters: * column - string or array of column names on which the value is calculated */ - mean : Miso.Product.define( function(columns, options) { + mean : Dataset.Product.define( function(columns, options) { var vals = []; _.each(columns, function(col) { vals.push(col.data); @@ -196,7 +195,7 @@ var type = columns[0].type; // convert the values to their appropriate numeric value - vals = _.map(vals, function(v) { return Miso.types[type].numeric(v); }); + vals = _.map(vals, function(v) { return Dataset.types[type].numeric(v); }); return _.mean(vals); }) diff --git a/src/sync.js b/src/sync.js index e7b1d7d..7e8e7c0 100644 --- a/src/sync.js +++ b/src/sync.js @@ -1,6 +1,6 @@ (function(global, _) { - var Miso = global.Miso || (global.Miso = {}); + var Dataset = global.Miso.Dataset; /** * A representation of an event as it is passed through the @@ -10,14 +10,14 @@ * deltas - array of deltas. * each delta: { changed : {}, old : {} } */ - Miso.Event = function(deltas) { + Dataset.Event = function(deltas) { if (!_.isArray(deltas)) { deltas = [deltas]; } this.deltas = deltas; }; - _.extend(Miso.Event.prototype, { + _.extend(Dataset.Event.prototype, { affectedColumns : function() { var cols = []; _.each(this.deltas, function(delta) { @@ -34,7 +34,7 @@ } }); - _.extend(Miso.Event, { + _.extend(Dataset.Event, { /** * Returns true if the event is a deletion */ @@ -71,7 +71,7 @@ //Event Related Methods - Miso.Events = {}; + Dataset.Events = {}; /** * Bind callbacks to dataset events @@ -82,7 +82,7 @@ * Returns * object being bound to. */ - Miso.Events.bind = function (ev, callback, context) { + Dataset.Events.bind = function (ev, callback, context) { var calls = this._callbacks || (this._callbacks = {}); var list = calls[ev] || (calls[ev] = {}); var tail = list.tail || (list.tail = list.next = {}); @@ -102,7 +102,7 @@ * Returns: * The object being unbound from. */ - Miso.Events.unbind = function(ev, callback) { + Dataset.Events.unbind = function(ev, callback) { var calls, node, prev; if (!ev) { this._callbacks = null; @@ -130,7 +130,7 @@ * Returns; * object being triggered on. */ - Miso.Events.trigger = function(eventName) { + Dataset.Events.trigger = function(eventName) { var node, calls, callback, args, ev, events = ['all', eventName]; if (!(calls = this._callbacks)) { return this; @@ -150,7 +150,7 @@ }; // Used to build event objects accross the application. - Miso.Events._buildEvent = function(delta) { - return new Miso.Event(delta); + Dataset.Events._buildEvent = function(delta) { + return new Dataset.Event(delta); }; }(this, _)); diff --git a/src/types.js b/src/types.js index d91b0bb..9a8a6c2 100644 --- a/src/types.js +++ b/src/types.js @@ -1,9 +1,9 @@ (function(global, _) { - var Miso = global.Miso || (global.Miso = {}); + var Dataset = global.Miso.Dataset; - Miso.typeOf = function(value, options) { - var types = _.keys(Miso.types), + Dataset.typeOf = function(value, options) { + var types = _.keys(Dataset.types), chosenType; //move string and mixed to the end @@ -11,7 +11,7 @@ types.push(types.splice(_.indexOf(types, 'mixed'), 1)[0]); chosenType = _.find(types, function(type) { - return Miso.types[type].test(value, options); + return Dataset.types[type].test(value, options); }); chosenType = _.isUndefined(chosenType) ? 'string' : chosenType; @@ -19,7 +19,7 @@ return chosenType; }; - Miso.types = { + Dataset.types = { mixed : { name : 'mixed', diff --git a/src/view.js b/src/view.js index 8b00082..9d3abcd 100644 --- a/src/view.js +++ b/src/view.js @@ -1,6 +1,6 @@ (function(global, _) { - var Miso = global.Miso; + var Dataset = global.Miso.Dataset; /** * A single column in a dataset @@ -15,14 +15,14 @@ * Returns: * new Miso.Column */ - Miso.Column = function(options) { + Dataset.Column = function(options) { _.extend(this, options); this._id = options.id || _.uniqueId(); this.data = options.data || []; return this; }; - _.extend(Miso.Column.prototype, { + _.extend(Dataset.Column.prototype, { /** * Converts any value to this column's type for a given position @@ -33,7 +33,7 @@ * number */ toNumeric : function(value) { - return Miso.types[this.type].numeric(value); + return Dataset.types[this.type].numeric(value); }, /** @@ -53,7 +53,7 @@ */ coerce : function() { this.data = _.map(this.data, function(datum) { - return Miso.types[this.type].coerce(datum, this); + return Dataset.types[this.type].coerce(datum, this); }, this); }, @@ -96,36 +96,36 @@ m += this.numericAt(j); } m /= this.data.length; - return Miso.types[this.type].coerce(m, this); + return Dataset.types[this.type].coerce(m, this); }, _median : function() { - return Miso.types[this.type].coerce(_.median(this.data), this); + return Dataset.types[this.type].coerce(_.median(this.data), this); }, _max : function() { var max = -Infinity; for (var j = 0; j < this.data.length; j++) { if (this.data[j] !== null) { - if (Miso.types[this.type].compare(this.data[j], max) > 0) { + if (Dataset.types[this.type].compare(this.data[j], max) > 0) { max = this.numericAt(j); } } } - return Miso.types[this.type].coerce(max, this); + return Dataset.types[this.type].coerce(max, this); }, _min : function() { var min = Infinity; for (var j = 0; j < this.data.length; j++) { if (this.data[j] !== null) { - if (Miso.types[this.type].compare(this.data[j], min) < 0) { + if (Dataset.types[this.type].compare(this.data[j], min) < 0) { min = this.numericAt(j); } } } - return Miso.types[this.type].coerce(min, this); + return Dataset.types[this.type].coerce(min, this); } }); @@ -140,7 +140,7 @@ * Returns * new Miso.Dataview. */ - Miso.DataView = function(options) { + Dataset.DataView = function(options) { if (typeof options !== "undefined") { options = options || (options = {}); @@ -152,14 +152,14 @@ } }; - _.extend(Miso.DataView.prototype, { + _.extend(Dataset.DataView.prototype, { _initialize: function(options) { // is this a syncable dataset? if so, pull // required methoMiso and mark this as a syncable dataset. if (this.parent.syncable === true) { - _.extend(this, Miso.Events); + _.extend(this, Dataset.Events); this.syncable = true; } @@ -172,8 +172,8 @@ // initialize columns. this._columns = this._selectData(); - Miso.Builder.cacheColumns(this); - Miso.Builder.cacheRows(this); + Dataset.Builder.cacheColumns(this); + Dataset.Builder.cacheRows(this); // bind to parent if syncable if (this.syncable) { @@ -193,7 +193,7 @@ // ===== ADD NEW ROW - if (typeof rowPos === "undefined" && Miso.Event.isAdd(d)) { + if (typeof rowPos === "undefined" && Dataset.Event.isAdd(d)) { // this is an add event, since we couldn't find an // existing row to update and now need to just add a new // one. Use the delta's changed properties as the new row @@ -227,7 +227,7 @@ // if this is a delete event OR the row no longer // passes the filter, remove it. - if (Miso.Event.isRemove(d) || + if (Dataset.Event.isRemove(d) || (this.filter.row && !this.filter.row(row))) { // Since this is now a delete event, we need to convert it @@ -262,7 +262,7 @@ * filter - object with optional columns array and filter object/function * options - Options. * Returns: - * new Miso.DataView + * new Miso.Dataset.DataView */ where : function(filter, options) { options = options || {}; @@ -275,7 +275,7 @@ options.parent = this; - return new Miso.DataView(options); + return new Dataset.DataView(options); }, _selectData : function() { @@ -285,7 +285,7 @@ // check if this column passes the column filter if (this.filter.columns(parentColumn)) { - selectedColumns.push(new Miso.Column({ + selectedColumns.push(new Dataset.Column({ name : parentColumn.name, data : [], type : parentColumn.type, @@ -394,7 +394,7 @@ * Miso.DataView. */ columns : function(columnsArray) { - return new Miso.DataView({ + return new Dataset.DataView({ filter : { columns : columnsArray }, parent : this }); @@ -523,7 +523,7 @@ // if we suddenly see values for data that didn't exist before as a column // just drop it. First fetch defines the column structure. if (typeof column !== "undefined") { - var Type = Miso.types[column.type]; + var Type = Dataset.types[column.type]; // test if value matches column type if (column.force || Type.test(row[column.name], column)) { @@ -538,7 +538,7 @@ } else { throw("incorrect value '" + row[column.name] + - "' of type " + Miso.typeOf(row[column.name], column) + + "' of type " + Dataset.typeOf(row[column.name], column) + " passed to column '" + column.name + "' with type " + column.type); } @@ -612,7 +612,7 @@ * the same as where */ rows : function(filter) { - return new Miso.DataView({ + return new Dataset.DataView({ filter : { rows : filter }, parent : this }); diff --git a/test/index.html b/test/index.html index e5d4145..c2b8162 100644 --- a/test/index.html +++ b/test/index.html @@ -20,6 +20,7 @@ }, }; + @@ -57,7 +58,6 @@ - diff --git a/test/unit/bugs.js b/test/unit/bugs.js index 97df6bc..f61d328 100644 --- a/test/unit/bugs.js +++ b/test/unit/bugs.js @@ -2,6 +2,7 @@ var Util = global.Util; var Miso = global.Miso || {}; + var Dataset = global.Miso.Dataset; module("Bugs"); @@ -15,7 +16,7 @@ { a : null, b : 5, c : null, d : true } ]; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, columns : [ { name : "c", type : "time", format: "YYYY" } @@ -37,9 +38,9 @@ var key = "0Asnl0xYK7V16dFpFVmZUUy1taXdFbUJGdGtVdFBXbFE", worksheet = "1"; - var ds = new Miso.Dataset({ - importer: Miso.Importers.GoogleSpreadsheet, - parser : Miso.Parsers.GoogleSpreadsheet, + var ds = new Dataset({ + importer: Dataset.Importers.GoogleSpreadsheet, + parser : Dataset.Parsers.GoogleSpreadsheet, key : key, worksheet: worksheet }); @@ -55,9 +56,9 @@ }); test("#133 - Google spreadsheet column duplicate name check (Regular)",1, function() { - var ds = new Miso.Dataset({ - parser: Miso.Parsers.GoogleSpreadsheet, - importer : Miso.Importers.GoogleSpreadsheet, + var ds = new Dataset({ + parser: Dataset.Parsers.GoogleSpreadsheet, + importer : Dataset.Importers.GoogleSpreadsheet, key : "0Al5UYaVoRpW3dHYxcEEwVXBvQkNmNjZOQ3dhSm53TGc", worksheet : "1" }); @@ -72,9 +73,9 @@ }); test("#133 - Google spreadsheet column duplicate name check (Fast)",1, function() { - var ds = new Miso.Dataset({ - parser: Miso.Parsers.GoogleSpreadsheet, - importer : Miso.Importers.GoogleSpreadsheet, + var ds = new Dataset({ + parser: Dataset.Parsers.GoogleSpreadsheet, + importer : Dataset.Importers.GoogleSpreadsheet, key : "0Al5UYaVoRpW3dHYxcEEwVXBvQkNmNjZOQ3dhSm53TGc", sheetName : "Sheet1", fast : true @@ -91,7 +92,7 @@ test("#133 - Strict mode column name duplicates",2, function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data: { columns : [ { name : "one", data : [1, 2, 3] }, { name : "two", data : [4, 5, 6] }, @@ -106,7 +107,7 @@ }, Error, "You have more than one column named \"one\""); - ds = new Miso.Dataset({ + ds = new Dataset({ data: { columns : [ { name : "one", data : [1, 2, 3] }, { name : "two", data : [4, 5, 6] }, @@ -128,7 +129,7 @@ var data = "A,B,C,B\n" + "1,2,3,4\n" + "5,6,7,8"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, delimiter : "," }); @@ -146,7 +147,7 @@ var data = "A,A,B,B,,\n" + "1,2,3,4,2,2\n" + "5,6,7,8,2,2"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, delimiter : "," }); @@ -167,7 +168,7 @@ var data = "A,\"\",\"\",B\n" + "1,2,3,4\n" + "5,6,7,8"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, delimiter : "," }); @@ -186,7 +187,7 @@ "Germany 29 25 28 29\n" + "France 29 28 28 30\n" + "Greece 35 33 33 33\n"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, delimiter : '\t' }); @@ -204,7 +205,7 @@ "Germany 29 25 28 29 99\n" + "France 29 28 28 30 32\n" + "Greece 35 33 33 33 2\n"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, delimiter : '\t' }); @@ -221,7 +222,7 @@ var data = "F,,,G\n" + "1,2,3,4\n" + "5,6,7,8"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, delimiter : "," }); @@ -236,7 +237,7 @@ test("#125 - Update in a string column with a string number shouldn't fail", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : [ { a: "g" , b : 1 }, { a: "sd" , b : 10 }, @@ -270,7 +271,7 @@ test("Zero vals convert to null csv delimited", 3, function() { stop(); - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : "data/withzeros.csv", delimiter : "," }); @@ -292,7 +293,7 @@ { a : 1, b : 0, c: 1} ]; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data }); @@ -311,7 +312,7 @@ { a : 1, b : 0, c: 1} ]; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data }); @@ -327,7 +328,7 @@ { seq : 0, q : 2, e : 1, x : 2 } ]; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, sync : true }); @@ -363,7 +364,7 @@ "5,3,4\n" + ""; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, delimiter : "," }); diff --git a/test/unit/core.js b/test/unit/core.js index a61efc9..b2d4acb 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -1,13 +1,13 @@ (function(global) { var Util = global.Util; - var Miso = global.Miso || {}; + var Dataset = global.Miso.Dataset; module("Code Structure"); test("predefined Miso components aren't overwritten", function() { - ok(Miso.Tester !== undefined); - equals(Miso.Tester.foo(), 44); + ok(global.Miso.Tester !== undefined); + equals(global.Miso.Tester.foo(), 44); }); module("Inheritance tests"); @@ -72,7 +72,7 @@ { a : 1, b : 0, c: 1} ]; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data }); @@ -89,7 +89,7 @@ { a : 1, b : 0, c: 1} ]; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data }); @@ -99,7 +99,7 @@ }}); verifyHasDataViewPrototype(dv); verifyNODatasetPrototypeMethods(dv); - Miso.DataView.prototype.lol = function() {}; + Dataset.DataView.prototype.lol = function() {}; ok(!_.isUndefined(this.lol)); }}); @@ -112,7 +112,7 @@ { a : 1, b : 0, c: 1} ]; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data }); @@ -120,14 +120,14 @@ var gb = ds.groupBy("a", ["b"]); verifyHasDataViewPrototype(gb); verifyDatasetPrototypeMethods(gb); - Miso.DataView.prototype.lol = function() {}; + Dataset.DataView.prototype.lol = function() {}; ok(!_.isUndefined(gb.lol)); }}); }); module("Fetching"); test("Basic fetch + success callback", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data: [ { one : 1, two : 4, three : 7 }, { one : 2, two : 5, three : 8 }, @@ -137,7 +137,7 @@ ds.fetch({ success: function() { - equals(this instanceof Miso.Dataset, true); + equals(this instanceof Dataset, true); ok(_.isEqual(this.columnNames(), ["one", "two", "three"])); } }); @@ -173,7 +173,7 @@ // }); test("Basic fetch + deferred callback", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data: [ { one : 1, two : 4, three : 7 }, { one : 2, two : 5, three : 8 }, @@ -182,20 +182,20 @@ }); _.when(ds.fetch()).then(function() { - equals(ds instanceof Miso.Dataset, true); + equals(ds instanceof Dataset, true); ok(_.isEqual(ds.columnNames(), ["one", "two", "three"])); }); }); test("Instantiation ready callback", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data: [ { one : 1, two : 4, three : 7 }, { one : 2, two : 5, three : 8 }, { one : 3, two : 6, three : 9 } ], ready : function() { - equals(this instanceof Miso.Dataset, true); + equals(this instanceof Dataset, true); ok(_.isEqual(this.columnNames(), ["one", "two", "three"])); } }).fetch(); @@ -204,13 +204,14 @@ module("Type raw extraction"); test("Numeric raw extraction", 2, function() { var ds = Util.baseSample(); + console.log('xxx', ds._columns[1]); equals(ds._columns[1].type, "number"); equals(ds._columns[1].numericAt(1), ds._columns[1].data[1]); }); test("String raw extraction", 3, function() { - var ds = new Miso.Dataset({ - data : Miso.alphabet_strict, + var ds = new Dataset({ + data : window.Miso.alphabet_strict, strict: true }).fetch({ success: function() { @@ -222,7 +223,7 @@ }); test("Time raw extraction", 2, function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : [ { 'character' : '12/31 2012' }, { 'character' : '01/31 2011' } diff --git a/test/unit/derived.js b/test/unit/derived.js index 45567c8..4dd2cb2 100644 --- a/test/unit/derived.js +++ b/test/unit/derived.js @@ -2,6 +2,7 @@ var Util = global.Util; var Miso = global.Miso || {}; + var Dataset = Miso.Dataset; module("CountBy"); var countData = { diff --git a/test/unit/helpers.js b/test/unit/helpers.js index 13ecdb4..69fa154 100644 --- a/test/unit/helpers.js +++ b/test/unit/helpers.js @@ -22,6 +22,7 @@ return ds; }; + Util.baseSample(); Util.baseSyncingSample = function() { var ds = null; @@ -42,4 +43,4 @@ return ds; }; -}(this)); \ No newline at end of file +}(this)); diff --git a/test/unit/importers.js b/test/unit/importers.js index 62b380f..8fd9d36 100644 --- a/test/unit/importers.js +++ b/test/unit/importers.js @@ -2,6 +2,7 @@ var Util = global.Util; var Miso = global.Miso || {}; + var Dataset = Miso.Dataset; function verifyImport(obj, strictData) { @@ -48,7 +49,7 @@ } test("Basic Strict Import through Dataset API", 47, function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : Miso.alphabet_strict, strict: true }); @@ -61,7 +62,7 @@ module("Column creation, coercion & type setting"); test("Manually creating a column", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ columns : [ { name : 'testOne' }, { name : 'testTwo', type: 'time' } @@ -73,7 +74,7 @@ }); test("Manual column type override", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : Miso.alphabet_strict, strict : true, columns : [ @@ -93,7 +94,7 @@ data.columns[1].data.push( moment() ); }); - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, strict: true, columns: [ @@ -110,7 +111,7 @@ test("Manual column type override with extra properties", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : [ { 'character' : '12/31 2012' }, { 'character' : '01/31 2011' } @@ -128,7 +129,7 @@ module("Obj Importer"); test("Convert object to dataset", 46, function() { - var ds = new Miso.Dataset({ data : Miso.alphabet_obj }); + var ds = new Dataset({ data : Miso.alphabet_obj }); _.when(ds.fetch()).then(function(){ verifyImport(Miso.alphabet_obj, ds); }); @@ -136,7 +137,7 @@ test("Basic json url fetch through Dataset API", 46, function() { var url = "data/alphabet_strict.json"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : url, jsonp : false, strict: true, @@ -150,7 +151,7 @@ }); test("Basic json url fetch through Dataset API + url is a function", 46, function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : function() { return "data/alphabet_strict.json"; }, @@ -167,7 +168,7 @@ test("Basic jsonp url fetch with Dataset API", 46, function() { var url = "data/alphabet_obj.json?callback="; - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : url, jsonp : true, extract: function(raw) { @@ -184,7 +185,7 @@ test("Basic jsonp url fetch with Dataset API without setting callback=", 46, function() { var url = "data/alphabet_obj.json"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : url, jsonp : true, extract: function(raw) { @@ -201,7 +202,7 @@ test("Basic jsonp url fetch with Dataset API setting a callback in the url", 46, function() { var url = "data/alphabet_obj.json?callback=testing"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : url, jsonp : true, extract: function(raw) { @@ -218,7 +219,7 @@ test("Basic jsonp url fetch with Dataset API without setting callback param but with other params", 46, function() { var url = "data/alphabet_obj.json?a=b"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : url, jsonp : true, extract: function(raw) { @@ -235,7 +236,7 @@ test("Basic jsonp url fetch with Dataset API & custom callback", 47, function() { var url = "data/alphabet_obj.json?callback="; - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : url, jsonp : true, extract: function(raw) { @@ -255,7 +256,7 @@ test("Basic delimiter parsing test with Dataset API", 46, function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : window.Miso.alphabet_csv, delimiter : "," }); @@ -266,9 +267,9 @@ test("Basic delimiter parsing test with Dataset API via url", 46, function() { stop(); - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : "data/alphabet.csv", - parser: Miso.Parsers.Delimited + parser: Dataset.Parsers.Delimited }); ds.fetch({ @@ -280,7 +281,7 @@ }); test("Basic delimiter parsing test with custom separator with Dataset API", 46, function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : window.Miso.alphabet_customseparator, delimiter : "###" }); @@ -290,7 +291,7 @@ }); test("Basic remote delimiter parsing test with custom separator with Dataset API", 46, function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : "data/alphabet_customseparator.json", delimiter : "###" }); @@ -307,7 +308,7 @@ "1,,5\n" + "5,,4"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, delimiter : ",", emptyValue : "CAT" @@ -327,7 +328,7 @@ "1,,4,5\n" + "5,3,4"; try { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, delimiter : "," }); @@ -343,7 +344,7 @@ "1,2,3\n" + "1,4,5\n" + "5,3,4"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, delimiter : ",", skipRows : 1 @@ -363,7 +364,7 @@ "1,2,3\n" + "1,4,5\n" + "5,3,4"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, delimiter : ",", skipRows : 3 @@ -381,7 +382,7 @@ "1,5\n" + "5,3,4"; try { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : data, delimiter : "," }); @@ -405,7 +406,7 @@ // ignoring id column, since that changes. for(var i = 1; i < d.length; i++) { - ok(_.isEqual(d._columns[i].data, obj._columns[i].data), "Expected: "+d._columns[i].data + " Got: " + window.Miso.google_spreadsheet_strict._columns[i].data); + ok(_.isEqual(d._columns[i].data, obj._columns[i].data), "Expected: "+d._columns[i].data + " Got: " + Miso.google_spreadsheet_strict._columns[i].data); } } @@ -414,13 +415,13 @@ var key = "0Asnl0xYK7V16dFpFVmZUUy1taXdFbUJGdGtVdFBXbFE"; var worksheet = "1"; - var ds = new Miso.Dataset({ - importer: Miso.Importers.GoogleSpreadsheet, - parser : Miso.Parsers.GoogleSpreadsheet, + var ds = new Dataset({ + importer: Dataset.Importers.GoogleSpreadsheet, + parser : Dataset.Parsers.GoogleSpreadsheet, key : key, worksheet: worksheet, ready : function() { - verifyGoogleSpreadsheet(this, window.Miso.google_spreadsheet_strict); + verifyGoogleSpreadsheet(this, Miso.google_spreadsheet_strict); start(); } }); @@ -432,12 +433,12 @@ var key = "0Asnl0xYK7V16dFpFVmZUUy1taXdFbUJGdGtVdFBXbFE"; var sheetName = "States"; - var ds = new Miso.Dataset({ + var ds = new Dataset({ key : key, sheetName : sheetName, fast : true, - importer: Miso.Importers.GoogleSpreadsheet, - parser : Miso.Parsers.GoogleSpreadsheet + importer: Dataset.Importers.GoogleSpreadsheet, + parser : Dataset.Parsers.GoogleSpreadsheet }); stop(); ds.fetch({ @@ -452,11 +453,11 @@ }); test("more columns than rows in Google Spreadsheet", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ key : "0AgzGUzeWla8QdDZLZnVieS1pOU5VRGxJNERvZ000SUE", worksheet : "1", - importer: Miso.Importers.GoogleSpreadsheet, - parser : Miso.Parsers.GoogleSpreadsheet + importer: Dataset.Importers.GoogleSpreadsheet, + parser : Dataset.Parsers.GoogleSpreadsheet }); ds.fetch({ success : function() { @@ -476,12 +477,12 @@ }); test("more columns than rows in Google Spreadsheet fast parse", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ key : "0AgzGUzeWla8QdDZLZnVieS1pOU5VRGxJNERvZ000SUE", worksheet : "1", fast : true, - importer: Miso.Importers.GoogleSpreadsheet, - parser : Miso.Parsers.GoogleSpreadsheet + importer: Dataset.Importers.GoogleSpreadsheet, + parser : Dataset.Parsers.GoogleSpreadsheet }); ds.fetch({ success : function() { @@ -507,7 +508,7 @@ var reqs = 5, madereqs = 0; expect(reqs); - var importer = new Miso.Importers.Polling({ + var importer = new Dataset.Importers.Polling({ url : "/poller/non_overlapping/5.json", interval : 100 }); @@ -543,7 +544,7 @@ var startId = Math.floor(Math.random() * 100); var reqs = 6, madereqs = 1, expectedSize = reqs * 10; - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : "/poller/non_overlapping/" + startId + ".json", interval : 100 }); @@ -614,7 +615,7 @@ events = [], addEvents = []; - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : "/poller/updated.json", interval : 100, uniqueAgainst : "name", @@ -704,7 +705,7 @@ var startId = Math.floor(Math.random() * 100); var reqs = 6, madereqs = 1, expectedSize = 10 + ((reqs-2) * 5); - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : "/poller/overlapping/" + startId + "/5.json", interval : 100, uniqueAgainst : "key" @@ -767,7 +768,7 @@ var startId = Math.floor(Math.random() * 100); var reqs = 6, madereqs = 1, expectedSize = 10; - var ds = new Miso.Dataset({ + var ds = new Dataset({ url : "/poller/overlapping/" + startId + "/5.json", interval : 100, resetOnFetch : true diff --git a/test/unit/products.js b/test/unit/products.js index 1e3705f..81cc413 100644 --- a/test/unit/products.js +++ b/test/unit/products.js @@ -1,7 +1,8 @@ (function(global) { - var Util = global.Util; - var Miso = global.Miso || {}; + var Util = global.Util; + var Miso = global.Miso || {}; + var Dataset = Miso.Dataset; module("Products :: Sum"); test("Basic Sum Product", function() { @@ -26,7 +27,7 @@ }); test("Time Sum Should Fail", 2, function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : [ { "one" : 1, "t" : "2010/01/13" }, { "one" : 5, "t" : "2010/05/15" }, @@ -92,7 +93,7 @@ }); test("Time Max Product", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : [ { "one" : 1, "t" : "2010/01/13" }, { "one" : 5, "t" : "2010/05/15" }, @@ -113,7 +114,7 @@ }); test("Time Max Product non syncable", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : [ { "one" : 1, "t" : "2010/01/13" }, { "one" : 5, "t" : "2010/05/15" }, @@ -169,7 +170,7 @@ }); test("Time Min Product", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : [ { "one" : 1, "t" : "2010/01/13" }, { "one" : 5, "t" : "2010/05/15" }, @@ -191,7 +192,7 @@ }); test("Time Min Product Non Syncable", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : [ { "one" : 1, "t" : "2010/01/13" }, { "one" : 5, "t" : "2010/05/15" }, @@ -209,7 +210,7 @@ }); test("Basic Mean Product", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : { columns : [ { name : 'vals', data : [1,2,3,4,5,6,7,8,9,10] }, @@ -251,7 +252,7 @@ }); test("Basic Mean Product Non Syncable", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : { columns : [ { name : 'vals', data : [1,2,3,4,5,6,7,8,9,10] }, @@ -284,7 +285,7 @@ }); test("Basic Time Mean Product", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : [ { "one" : 1, "t" : "2010/01/01" }, { "one" : 5, "t" : "2010/01/15" }, @@ -360,7 +361,7 @@ counter = 0; equals(_.isUndefined(max.bind), true); - equals(Miso.typeOf(max), "number"); + equals(Dataset.typeOf(max), "number"); }); @@ -385,7 +386,7 @@ test("Defining a custom product", function() { var ds = Util.baseSyncingSample(); - var min = Miso.Product.define(function() { + var min = Dataset.Product.define(function() { var min = Infinity; _.each(this._column('one').data, function(value) { if (value < min) { @@ -406,7 +407,7 @@ test("Defining a new product on the Miso prototype", function() { var ds = Util.baseSyncingSample(); - Miso.Dataset.prototype.custom = Miso.Product.define(function() { + Dataset.prototype.custom = Dataset.Product.define(function() { var min = Infinity; _.each(this._column('one').data, function(value) { if (value < min) { @@ -429,7 +430,7 @@ test("Defining a new product a dataset", function() { var ds = Util.baseSyncingSample(); - ds.custom = Miso.Product.define(function() { + ds.custom = Dataset.Product.define(function() { var min = Infinity; _.each(this._column('one').data, function(value) { if (value < min) { diff --git a/test/unit/types.js b/test/unit/types.js index a2e5962..f8615f7 100644 --- a/test/unit/types.js +++ b/test/unit/types.js @@ -1,31 +1,31 @@ (function(global) { var Util = global.Util; - var Miso = global.Miso || {}; + var Dataset = global.Miso.Dataset; var numbers = ['123', '0.34', '.23']; var not_numbers = [null, NaN,undefined]; - module("Miso Numeric Type"); + module("Dataset Numeric Type"); test("Check number type", function() { var notNumbers = ['a', {}, 'll22']; _.each(numbers, function(num) { - ok(Miso.typeOf(num) === "number", "Value should be number"); - ok(Miso.types.number.test(num), "Should return true for a number"); + ok(Dataset.typeOf(num) === "number", "Value should be number"); + ok(Dataset.types.number.test(num), "Should return true for a number"); }); _.each(notNumbers, function(nn) { - ok(Miso.typeOf(nn) !== "number", "Value should not be number " + nn); - ok(!Miso.types.number.test(nn), "Should not return true for a number " + nn); + ok(Dataset.typeOf(nn) !== "number", "Value should not be number " + nn); + ok(!Dataset.types.number.test(nn), "Should not return true for a number " + nn); }); }); test("Check all non numeric values return null on numeric", function() { expect(10); - _.each(Miso.types, function(type) { + _.each(Dataset.types, function(type) { // not checking undefined - we either coerrced it out and can't computationally // derive it like a NaN _.each([NaN, null], function(not_a_number) { @@ -37,7 +37,7 @@ test("Check all non numeric values return null on coerce", function() { expect(15); - _.each(Miso.types, function(type) { + _.each(Dataset.types, function(type) { _.each(not_numbers, function(not_a_number) { ok(type.coerce(not_a_number) === null, "["+type.name+"] " + not_a_number + " is represented as " + type.coerce(not_a_number)); }); @@ -48,7 +48,7 @@ test("Coerce number type", function() { var coerced = [123, 0.34, 0.23]; _.each(numbers, function(num, i) { - equals(Miso.types.number.coerce(num), coerced[i], "Should return true for a number"); + equals(Dataset.types.number.coerce(num), coerced[i], "Should return true for a number"); }); }); @@ -56,89 +56,89 @@ test("Coerce to null", function() { var coerced = ['foo', undefined, NaN, {}]; _.each(coerced, function(num, i) { - equals(Miso.types.number.coerce(coerced[i]), null, "Should return null for invalid input"); + equals(Dataset.types.number.coerce(coerced[i]), null, "Should return null for invalid input"); }); }); test("Compare number type", function() { - equals(Miso.types.number.compare(10,20), -1); - equals(Miso.types.number.compare(20,10), 1); - equals(Miso.types.number.compare(10,10), 0); - equals(Miso.types.number.compare(20,200), -1); - equals(Miso.types.number.compare(0, 0), 0); - equals(Miso.types.number.compare(-30, -40), 1); - equals(Miso.types.number.compare(-30, 0), -1); + equals(Dataset.types.number.compare(10,20), -1); + equals(Dataset.types.number.compare(20,10), 1); + equals(Dataset.types.number.compare(10,10), 0); + equals(Dataset.types.number.compare(20,200), -1); + equals(Dataset.types.number.compare(0, 0), 0); + equals(Dataset.types.number.compare(-30, -40), 1); + equals(Dataset.types.number.compare(-30, 0), -1); }); - module("Miso Boolean Type"); + module("Dataset Boolean Type"); var booleans = ['true', 'false', true]; test("Check boolean type", function() { var notBooleans = [1, 'foo', {}]; _.each(booleans, function(bool) { - ok(Miso.typeOf(bool) === "boolean", "Value should be boolean"); - ok(Miso.types.boolean.test(bool), "Should return true for a bool"); + ok(Dataset.typeOf(bool) === "boolean", "Value should be boolean"); + ok(Dataset.types.boolean.test(bool), "Should return true for a bool"); }); _.each(notBooleans, function(nb) { - ok(Miso.typeOf(nb) !== "boolean", nb+" Value should not be number"); - ok(!Miso.types.boolean.test(nb), nb+" Should not return true for a boolean"); + ok(Dataset.typeOf(nb) !== "boolean", nb+" Value should not be number"); + ok(!Dataset.types.boolean.test(nb), nb+" Should not return true for a boolean"); }); }); test("Coerce boolean type", function() { var coerced = [true, false, true]; _.each(booleans, function(num, i) { - equals(Miso.types.boolean.coerce(num), coerced[i], "Should return true for a boolean"); + equals(Dataset.types.boolean.coerce(num), coerced[i], "Should return true for a boolean"); }); }); test("Compare boolean type", function() { var results = [0, -1]; _.each([true, false], function(num, i) { - equals(Miso.types.boolean.compare(num, true), results[i], "Should return true for a boolean"); + equals(Dataset.types.boolean.compare(num, true), results[i], "Should return true for a boolean"); }); }); test("Numeric conversion", function() { - equals(Miso.types.boolean.numeric(true), 1, "True returns 1"); - equals(Miso.types.boolean.numeric(false), 0, "False returns 0"); + equals(Dataset.types.boolean.numeric(true), 1, "True returns 1"); + equals(Dataset.types.boolean.numeric(false), 0, "False returns 0"); }); test("Check weird types", function() { - equals(Miso.types.string.compare(null, "a"), -1); - equals(Miso.types.string.compare("a", null), 1); - equals(Miso.types.string.compare(null, null), 0); - equals(Miso.types.string.compare(null, undefined), 0); - equals(Miso.types.string.compare(undefined, undefined), 0); - equals(Miso.types.string.compare(undefined, null), 0); - - equals(Miso.types.number.compare(null, 1), -1); - equals(Miso.types.number.compare(null, 0), -1); - equals(Miso.types.number.compare(1, null), 1); - equals(Miso.types.number.compare(0, null), 1); - equals(Miso.types.number.compare(null, null), 0); - equals(Miso.types.number.compare(null, undefined), 0); - equals(Miso.types.number.compare(undefined, undefined), 0); - equals(Miso.types.number.compare(undefined, null), 0); - - equals(Miso.types.boolean.compare(null, true), -1); - equals(Miso.types.boolean.compare(true, null), 1); - equals(Miso.types.boolean.compare(null, null), 0); - equals(Miso.types.boolean.compare(null, undefined), 0); - equals(Miso.types.boolean.compare(undefined, undefined), 0); - equals(Miso.types.boolean.compare(undefined, null), 0); - - equals(Miso.types.time.compare(null, moment()), -1); - equals(Miso.types.time.compare(moment(), null), 1); - equals(Miso.types.time.compare(null, null), 0); - equals(Miso.types.time.compare(null, undefined), 0); - equals(Miso.types.time.compare(undefined, undefined), 0); - equals(Miso.types.time.compare(undefined, null), 0); - }); - - module("Miso Time Type"); + equals(Dataset.types.string.compare(null, "a"), -1); + equals(Dataset.types.string.compare("a", null), 1); + equals(Dataset.types.string.compare(null, null), 0); + equals(Dataset.types.string.compare(null, undefined), 0); + equals(Dataset.types.string.compare(undefined, undefined), 0); + equals(Dataset.types.string.compare(undefined, null), 0); + + equals(Dataset.types.number.compare(null, 1), -1); + equals(Dataset.types.number.compare(null, 0), -1); + equals(Dataset.types.number.compare(1, null), 1); + equals(Dataset.types.number.compare(0, null), 1); + equals(Dataset.types.number.compare(null, null), 0); + equals(Dataset.types.number.compare(null, undefined), 0); + equals(Dataset.types.number.compare(undefined, undefined), 0); + equals(Dataset.types.number.compare(undefined, null), 0); + + equals(Dataset.types.boolean.compare(null, true), -1); + equals(Dataset.types.boolean.compare(true, null), 1); + equals(Dataset.types.boolean.compare(null, null), 0); + equals(Dataset.types.boolean.compare(null, undefined), 0); + equals(Dataset.types.boolean.compare(undefined, undefined), 0); + equals(Dataset.types.boolean.compare(undefined, null), 0); + + equals(Dataset.types.time.compare(null, moment()), -1); + equals(Dataset.types.time.compare(moment(), null), 1); + equals(Dataset.types.time.compare(null, null), 0); + equals(Dataset.types.time.compare(null, undefined), 0); + equals(Dataset.types.time.compare(undefined, undefined), 0); + equals(Dataset.types.time.compare(undefined, null), 0); + }); + + module("Dataset Time Type"); test("Check date parsing formats", function() { var testtimes = [ @@ -177,14 +177,14 @@ { input : "12:05:30 -0400", format : "hh:mm:ss ZZ" } ]; _.each(testtimes, function(t) { - ok(Miso.types.time.test(t.input, {format : t.format}), t.input); - ok(Miso.types.time.coerce(t.input, {format:t.format}).valueOf(), moment(t.input, t.format).valueOf()); + ok(Dataset.types.time.test(t.input, {format : t.format}), t.input); + ok(Dataset.types.time.coerce(t.input, {format:t.format}).valueOf(), moment(t.input, t.format).valueOf()); }); }); test("Check date type", function() { - ok(Miso.types.time.test("22/22/2001"), "date in correct format"); - ok(!Miso.types.time.test("20"), "date incorrect format"); + ok(Dataset.types.time.test("22/22/2001"), "date in correct format"); + ok(!Dataset.types.time.test("20"), "date incorrect format"); }); test("Compare date type", function() { @@ -192,27 +192,27 @@ m2 = moment("2011/05/05", "YYYY/MM/DD"), m3 = moment("2011/05/01", "YYYY/MM/DD"); - equals(Miso.types.time.compare(m,m2), -1); - equals(Miso.types.time.compare(m2,m), 1); - equals(Miso.types.time.compare(m3,m), 0); + equals(Dataset.types.time.compare(m,m2), -1); + equals(Dataset.types.time.compare(m2,m), 1); + equals(Dataset.types.time.compare(m3,m), 0); }); - module("Miso String Type"); + module("Dataset String Type"); test("Compare string type", function() { - equals(Miso.types.string.compare("A", "B"), -1); - equals(Miso.types.string.compare("C", "B"), 1); - equals(Miso.types.string.compare("bbb", "bbb"), 0); - equals(Miso.types.string.compare("bbb", "bbbb"), -1); - equals(Miso.types.string.compare("bbb", "bbbb"), -1); - equals(Miso.types.string.compare("bbbb", "bbb"), 1); - equals(Miso.types.string.compare("bbb", "bbb"), 0); + equals(Dataset.types.string.compare("A", "B"), -1); + equals(Dataset.types.string.compare("C", "B"), 1); + equals(Dataset.types.string.compare("bbb", "bbb"), 0); + equals(Dataset.types.string.compare("bbb", "bbbb"), -1); + equals(Dataset.types.string.compare("bbb", "bbbb"), -1); + equals(Dataset.types.string.compare("bbbb", "bbb"), 1); + equals(Dataset.types.string.compare("bbb", "bbb"), 0); }); test("String type returns 0 or coerced form", function() { - equals(Miso.types.string.numeric("A"), null); - equals(Miso.types.string.numeric(null), null); - equals(Miso.types.string.numeric("99"), 99); - equals(Miso.types.string.numeric("99.3"), 99.3); + equals(Dataset.types.string.numeric("A"), null); + equals(Dataset.types.string.numeric(null), null); + equals(Dataset.types.string.numeric("99"), 99); + equals(Dataset.types.string.numeric("99.3"), 99.3); }); }(this)); diff --git a/test/unit/views.js b/test/unit/views.js index 4b49980..d344dfb 100644 --- a/test/unit/views.js +++ b/test/unit/views.js @@ -1,7 +1,7 @@ (function(global) { var Util = global.Util; - var Miso = global.Miso || {}; + var Dataset = global.Miso.Dataset; module("Columns"); @@ -36,7 +36,7 @@ }); test("Column median", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : { columns : [ { name : 'vals', data : [1,2,3,4,5,6,7,8,9,10] }, @@ -54,7 +54,7 @@ }); test("Column mean", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : { columns : [ { name : 'vals', data : [1,2,3,4,5,6,7,8,9,10] }, @@ -72,7 +72,7 @@ }); test("Column before function", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data : { columns : [ { name : 'vals', data : [1,2,3,4,5,6,7,8,9,10] } @@ -310,7 +310,7 @@ module("Views :: Syncing"); delta.old[col] = oldVal; delta.changed[col] = 100; - var event = Miso.Events._buildEvent(delta); + var event = Dataset.Events._buildEvent(delta); // trigger view sync with delta // view.sync(delta); @@ -349,7 +349,7 @@ module("Views :: Syncing"); delta.old[col] = oldVal; delta.changed[col] = 100; - var event = Miso.Events._buildEvent(delta); + var event = Dataset.Events._buildEvent(delta); // trigger view sync with delta // view.sync(delta); @@ -416,7 +416,7 @@ module("Views :: Syncing"); delta.old[colname] = oldVal; delta.changed[colname] = 100; - var event = Miso.Events._buildEvent(delta); + var event = Dataset.Events._buildEvent(delta); // trigger dataset change ds.trigger("change", event); @@ -445,7 +445,7 @@ module("Views :: Syncing"); }; // create event representing deletion - var event = Miso.Events._buildEvent(delta); + var event = Dataset.Events._buildEvent(delta); // delete actual row ds._remove( ds._rowIdByPosition[0] ); @@ -492,7 +492,7 @@ module("Views :: Syncing"); }; // create event representing addition - var event = Miso.Events._buildEvent(delta); + var event = Dataset.Events._buildEvent(delta); // for now, we aren't adding the actual data to the original dataset // just simulating that addition. Eventually when we ammend the api @@ -533,7 +533,7 @@ module("Views :: Syncing"); }; // create event representing addition - var event = Miso.Events._buildEvent(delta); + var event = Dataset.Events._buildEvent(delta); // for now, we aren't adding the actual data to the original dataset // just simulating that addition. Eventually when we ammend the api @@ -558,7 +558,7 @@ module("Views :: Syncing"); }); test("Basic Sort", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data: { columns : [ { name : "one", data : [10, 2, 3, 14, 3, 4] }, { name : "two", data : [4, 5, 6, 1, 1, 1] }, @@ -584,7 +584,7 @@ module("Views :: Syncing"); }); test("Sort with options param", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data: { columns : [ { name : "one", data : [10, 2, 3, 14, 3, 4] }, { name : "two", data : [4, 5, 6, 1, 1, 1] }, @@ -611,7 +611,7 @@ module("Views :: Syncing"); }); test("setting sort comparator when sorting", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data: { columns : [ { name : "one", data : [10, 2, 3, 14, 3, 4] }, { name : "two", data : [4, 5, 6, 1, 1, 1] }, @@ -635,7 +635,7 @@ module("Views :: Syncing"); }); test("Basic Sort reverse", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data: { columns : [ { name : "one", data : [10, 2, 6, 14, 3, 4] }, { name : "two", data : [4, 5, 6, 1, 1, 1] }, @@ -659,7 +659,7 @@ module("Views :: Syncing"); }); test("Sort in init", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data: { columns : [ { name : "one", data : [10, 2, 6, 14, 3, 4] }, { name : "two", data : [4, 5, 6, 1, 1, 1] }, @@ -680,7 +680,7 @@ module("Views :: Syncing"); }); test("Add row in sorted order", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data: { columns : [ { name : "one", data : [10, 2, 6, 14, 3, 4] }, { name : "two", data : [4, 5, 6, 1, 1, 1] }, @@ -706,7 +706,7 @@ module("Views :: Syncing"); }); test("Add row in reverse sorted order", function() { - var ds = new Miso.Dataset({ + var ds = new Dataset({ data: { columns : [ { name : "one", data : [10, 2, 6, 14, 3, 4] }, { name : "two", data : [4, 5, 6, 1, 1, 1] }, @@ -733,7 +733,7 @@ module("Views :: Syncing"); module("export"); test("Export to json", function(){ - var ds = new Miso.Dataset({ + var ds = new Dataset({ data: { columns : [ { name : "one", data : [10, 2, 6, 14, 3, 4] }, { name : "two", data : [4, 5, 6, 1, 1, 1] }, From 29f4226b1bd672ece448bf9be7dc2442ab8f7c69 Mon Sep 17 00:00:00 2001 From: Alex Graul Date: Sun, 23 Sep 2012 22:00:06 +0100 Subject: [PATCH 12/12] updated build and remove a console log --- grunt.js | 5 +++-- src/dataset.js | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/grunt.js b/grunt.js index addaeb0..1cbae80 100644 --- a/grunt.js +++ b/grunt.js @@ -28,12 +28,13 @@ module.exports = function(grunt) { dest: "dist/miso.ds.<%= pkg.version %>.js", src: [ "", + "src/constructor.js", + "src/view.js", + "src/dataset.js", "src/types.js", "src/sync.js", "src/builder.js", - "src/view.js", "src/product.js", - "src/dataset.js", "src/derived.js", "src/importer.js", "src/importers/local.js", diff --git a/src/dataset.js b/src/dataset.js index 6188a50..683035a 100644 --- a/src/dataset.js +++ b/src/dataset.js @@ -12,7 +12,6 @@ Version 0.0.1.2 // take on miso dataview's prototype Dataset.prototype = new Dataset.DataView(); - console.log(Dataset.prototype); // add dataset methods to dataview. _.extend(Dataset.prototype, {