underscore_extensions 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rvmrc CHANGED
@@ -1 +1 @@
1
- rvm 1.9.3@underscore_extensions --create
1
+ rvm 1.9.3-p125-perf@underscore_extensions --create
@@ -13,6 +13,32 @@
13
13
  }, obj);
14
14
  }
15
15
 
16
+ function mergeSort(array, comparison) {
17
+ function merge(left, right, comparison) {
18
+ var result = [];
19
+ while ((left.length > 0) && (right.length > 0)) {
20
+ if (comparison(left[0], right[0]) <= 0) {
21
+ result.push(left.shift());
22
+ } else {
23
+ result.push(right.shift());
24
+ }
25
+ }
26
+ while (left.length > 0) {
27
+ result.push(left.shift());
28
+ }
29
+ while (right.length > 0) {
30
+ result.push(right.shift());
31
+ }
32
+ return result;
33
+ }
34
+
35
+ if (array.length < 2) {
36
+ return array;
37
+ }
38
+ var middle = Math.ceil(array.length / 2);
39
+ return merge(mergeSort(array.slice(0, middle), comparison), mergeSort(array.slice(middle), comparison), comparison);
40
+ }
41
+
16
42
  _.mixin({
17
43
  classify: function(str) {
18
44
  var s = _(str).trim().replace(/(\-|_|\s)+(.)?/g, function(match, separator, chr) {
@@ -20,20 +46,6 @@
20
46
  });
21
47
  return s.charAt(0).toUpperCase() + s.substring(1);
22
48
  },
23
- except: function(obj) {
24
- if (obj === null) { return obj; }
25
- var args;
26
- if (arguments.length === 2 && _(arguments[1]).isArray()) {
27
- args = arguments[1];
28
- } else {
29
- args = Array.prototype.slice.call(arguments, 1);
30
- }
31
- var result = _(obj).clone();
32
- _(args).each(function(arg) {
33
- delete result[arg];
34
- });
35
- return result;
36
- },
37
49
  namespace: function(obj, ns) {
38
50
  if (arguments.length === 2) {
39
51
  if (_(ns).isArray()) {
@@ -45,21 +57,29 @@
45
57
  return namespace(obj, Array.prototype.slice.call(arguments, 1));
46
58
  }
47
59
  },
48
- only: function(obj) {
49
- function only() {
50
- var args = _(arguments);
51
- return _(obj).inject(function(result, value, key) {
52
- if (args.include(key)) {
53
- result[key] = value;
54
- }
55
- return result;
56
- }, {});
57
- }
58
- if (arguments.length === 2 && _(arguments[1]).isArray()) {
59
- return only.apply(_, arguments[1]);
60
- } else {
61
- return only.apply(_, Array.prototype.slice.call(arguments, 1));
62
- }
60
+ stableSortBy: function(obj, val, context) {
61
+ var iterator = _.isFunction(val) ? val : function(obj) {
62
+ return obj[val];
63
+ };
64
+ var toSort = _.map(obj, function(value, index, list) {
65
+ return {
66
+ value: value,
67
+ criteria: iterator.call(context, value, index, list)
68
+ };
69
+ });
70
+ var comparator = function(left, right) {
71
+ var a = left.criteria, b = right.criteria;
72
+ if (a === void 0) {
73
+ return 1;
74
+ }
75
+ if (b === void 0) {
76
+ return -1;
77
+ }
78
+ return a < b ? -1 : a > b ? 1 : 0;
79
+ };
80
+
81
+ toSort = mergeSort(toSort, comparator);
82
+ return _.pluck(toSort, 'value');
63
83
  }
64
84
  });
65
85
 
@@ -1,5 +1,5 @@
1
1
  module UnderscoreExtensions
2
- VERSION = '0.1.1'
3
- UNDERSCORE_VERSION = '1.3.3'
2
+ VERSION = '0.2.0'
3
+ UNDERSCORE_VERSION = '1.4.2'
4
4
  UNDERSCORE_STRING_VERSION = '2.3.0'
5
5
  end
@@ -5,18 +5,6 @@ describe('_', function() {
5
5
  });
6
6
  });
7
7
 
8
- describe('#except', function() {
9
- it('should return the object without the keys specified', function() {
10
- expect(_({a: 'a', b: 'b', c: 'c'}).except('b', 'c')).toEqual({a: 'a'});
11
- });
12
-
13
- describe('when the keys are specified as an array', function() {
14
- it('should return the object without the keys specified', function() {
15
- expect(_({a: 'a', b: 'b', c: 'c'}).except(['b', 'c'])).toEqual({a: 'a'});
16
- });
17
- });
18
- });
19
-
20
8
  describe('#namespace', function() {
21
9
  describe('when the namespace is passed as a single parameter with period separator', function() {
22
10
  it('should create the namespace', function() {
@@ -70,26 +58,6 @@ describe('_', function() {
70
58
  });
71
59
  });
72
60
 
73
- describe('#only', function() {
74
- it('should return an empty object if no keys are specified', function() {
75
- expect(_({a: 'a', b: 'b', c: 'c'}).only()).toEqual({});
76
- });
77
-
78
- it('should return the object only the keys specified', function() {
79
- expect(_({a: 'aa', b: 'bb', c: 'cc'}).only('a', 'c')).toEqual({a: 'aa', c: 'cc'});
80
- });
81
-
82
- describe('when the keys are specified as an array', function() {
83
- it('should return an empty object if no keys are specified', function() {
84
- expect(_({a: 'a', b: 'b', c: 'c'}).only([])).toEqual({});
85
- });
86
-
87
- it('should return the object only the keys specified', function() {
88
- expect(_({a: 'aa', b: 'bb', c: 'cc'}).only(['a', 'c'])).toEqual({a: 'aa', c: 'cc'});
89
- });
90
- });
91
- });
92
-
93
61
  describe('#pluralize', function() {
94
62
  it("should pluralize a model name", function() {
95
63
  expect(_('point').pluralize()).toEqual('points');
@@ -124,4 +92,12 @@ describe('_', function() {
124
92
  expect(_('foos').singularize({skip: 'foos'})).toEqual('foos');
125
93
  });
126
94
  });
95
+
96
+ describe("#stableSortBy", function() {
97
+ it("should sort the items by the provided iterator, through a stable sort", function() {
98
+ var toSort = [{a: 1}, {b: 1}, {c: 1}, {d: 1}, {e: 2}, {f:1}, {g: 1}];
99
+ var sorted = _(toSort).stableSortBy(function(obj) { return _(obj).values()[0]; });
100
+ expect(sorted).toEqual([{a: 1}, {b: 1}, {c: 1}, {d: 1}, {f:1}, {g: 1}, {e: 2}]);
101
+ });
102
+ });
127
103
  });
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  s.add_development_dependency 'fuubar'
22
- s.add_development_dependency 'jasmine', '>= 1.2.1'
22
+ s.add_development_dependency 'jasmine', ">= 1.2.1"
23
23
  s.add_development_dependency 'jshint_on_rails'
24
24
  s.add_development_dependency 'thin'
25
25
  s.add_runtime_dependency 'rails', '>= 3.1'
@@ -1,10 +1,7 @@
1
- // Underscore.js 1.3.3
1
+ // Underscore.js 1.4.2
2
+ // http://underscorejs.org
2
3
  // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
3
- // Underscore is freely distributable under the MIT license.
4
- // Portions of Underscore are inspired or borrowed from Prototype,
5
- // Oliver Steele's Functional, and John Resig's Micro-Templating.
6
- // For all details and documentation:
7
- // http://documentcloud.github.com/underscore
4
+ // Underscore may be freely distributed under the MIT license.
8
5
 
9
6
  (function() {
10
7
 
@@ -24,7 +21,9 @@
24
21
  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
25
22
 
26
23
  // Create quick reference variables for speed access to core prototypes.
27
- var slice = ArrayProto.slice,
24
+ var push = ArrayProto.push,
25
+ slice = ArrayProto.slice,
26
+ concat = ArrayProto.concat,
28
27
  unshift = ArrayProto.unshift,
29
28
  toString = ObjProto.toString,
30
29
  hasOwnProperty = ObjProto.hasOwnProperty;
@@ -46,7 +45,11 @@
46
45
  nativeBind = FuncProto.bind;
47
46
 
48
47
  // Create a safe reference to the Underscore object for use below.
49
- var _ = function(obj) { return new wrapper(obj); };
48
+ var _ = function(obj) {
49
+ if (obj instanceof _) return obj;
50
+ if (!(this instanceof _)) return new _(obj);
51
+ this._wrapped = obj;
52
+ };
50
53
 
51
54
  // Export the Underscore object for **Node.js**, with
52
55
  // backwards-compatibility for the old `require()` API. If we're in
@@ -62,7 +65,7 @@
62
65
  }
63
66
 
64
67
  // Current version.
65
- _.VERSION = '1.3.3';
68
+ _.VERSION = '1.4.2';
66
69
 
67
70
  // Collection Functions
68
71
  // --------------------
@@ -76,7 +79,7 @@
76
79
  obj.forEach(iterator, context);
77
80
  } else if (obj.length === +obj.length) {
78
81
  for (var i = 0, l = obj.length; i < l; i++) {
79
- if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
82
+ if (iterator.call(context, obj[i], i, obj) === breaker) return;
80
83
  }
81
84
  } else {
82
85
  for (var key in obj) {
@@ -96,7 +99,6 @@
96
99
  each(obj, function(value, index, list) {
97
100
  results[results.length] = iterator.call(context, value, index, list);
98
101
  });
99
- if (obj.length === +obj.length) results.length = obj.length;
100
102
  return results;
101
103
  };
102
104
 
@@ -128,11 +130,24 @@
128
130
  if (obj == null) obj = [];
129
131
  if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
130
132
  if (context) iterator = _.bind(iterator, context);
131
- return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
133
+ return arguments.length > 2 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
134
+ }
135
+ var length = obj.length;
136
+ if (length !== +length) {
137
+ var keys = _.keys(obj);
138
+ length = keys.length;
132
139
  }
133
- var reversed = _.toArray(obj).reverse();
134
- if (context && !initial) iterator = _.bind(iterator, context);
135
- return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
140
+ each(obj, function(value, index, list) {
141
+ index = keys ? keys[--length] : --length;
142
+ if (!initial) {
143
+ memo = obj[index];
144
+ initial = true;
145
+ } else {
146
+ memo = iterator.call(context, memo, obj[index], index, list);
147
+ }
148
+ });
149
+ if (!initial) throw new TypeError('Reduce of empty array with no initial value');
150
+ return memo;
136
151
  };
137
152
 
138
153
  // Return the first value which passes a truth test. Aliased as `detect`.
@@ -174,6 +189,7 @@
174
189
  // Delegates to **ECMAScript 5**'s native `every` if available.
175
190
  // Aliased as `all`.
176
191
  _.every = _.all = function(obj, iterator, context) {
192
+ iterator || (iterator = _.identity);
177
193
  var result = true;
178
194
  if (obj == null) return result;
179
195
  if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
@@ -197,9 +213,9 @@
197
213
  return !!result;
198
214
  };
199
215
 
200
- // Determine if a given value is included in the array or object using `===`.
201
- // Aliased as `contains`.
202
- _.include = _.contains = function(obj, target) {
216
+ // Determine if the array or object contains a given value (using `===`).
217
+ // Aliased as `include`.
218
+ _.contains = _.include = function(obj, target) {
203
219
  var found = false;
204
220
  if (obj == null) return found;
205
221
  if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
@@ -213,7 +229,7 @@
213
229
  _.invoke = function(obj, method) {
214
230
  var args = slice.call(arguments, 2);
215
231
  return _.map(obj, function(value) {
216
- return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
232
+ return (_.isFunction(method) ? method : value[method]).apply(value, args);
217
233
  });
218
234
  };
219
235
 
@@ -222,9 +238,25 @@
222
238
  return _.map(obj, function(value){ return value[key]; });
223
239
  };
224
240
 
241
+ // Convenience version of a common use case of `filter`: selecting only objects
242
+ // with specific `key:value` pairs.
243
+ _.where = function(obj, attrs) {
244
+ if (_.isEmpty(attrs)) return [];
245
+ return _.filter(obj, function(value) {
246
+ for (var key in attrs) {
247
+ if (attrs[key] !== value[key]) return false;
248
+ }
249
+ return true;
250
+ });
251
+ };
252
+
225
253
  // Return the maximum element or (element-based computation).
254
+ // Can't optimize arrays of integers longer than 65,535 elements.
255
+ // See: https://bugs.webkit.org/show_bug.cgi?id=80797
226
256
  _.max = function(obj, iterator, context) {
227
- if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj);
257
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
258
+ return Math.max.apply(Math, obj);
259
+ }
228
260
  if (!iterator && _.isEmpty(obj)) return -Infinity;
229
261
  var result = {computed : -Infinity};
230
262
  each(obj, function(value, index, list) {
@@ -236,7 +268,9 @@
236
268
 
237
269
  // Return the minimum element (or element-based computation).
238
270
  _.min = function(obj, iterator, context) {
239
- if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj);
271
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
272
+ return Math.min.apply(Math, obj);
273
+ }
240
274
  if (!iterator && _.isEmpty(obj)) return Infinity;
241
275
  var result = {computed : Infinity};
242
276
  each(obj, function(value, index, list) {
@@ -248,67 +282,94 @@
248
282
 
249
283
  // Shuffle an array.
250
284
  _.shuffle = function(obj) {
251
- var shuffled = [], rand;
252
- each(obj, function(value, index, list) {
253
- rand = Math.floor(Math.random() * (index + 1));
254
- shuffled[index] = shuffled[rand];
285
+ var rand;
286
+ var index = 0;
287
+ var shuffled = [];
288
+ each(obj, function(value) {
289
+ rand = _.random(index++);
290
+ shuffled[index - 1] = shuffled[rand];
255
291
  shuffled[rand] = value;
256
292
  });
257
293
  return shuffled;
258
294
  };
259
295
 
296
+ // An internal function to generate lookup iterators.
297
+ var lookupIterator = function(value) {
298
+ return _.isFunction(value) ? value : function(obj){ return obj[value]; };
299
+ };
300
+
260
301
  // Sort the object's values by a criterion produced by an iterator.
261
- _.sortBy = function(obj, val, context) {
262
- var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
302
+ _.sortBy = function(obj, value, context) {
303
+ var iterator = lookupIterator(value);
263
304
  return _.pluck(_.map(obj, function(value, index, list) {
264
305
  return {
265
306
  value : value,
307
+ index : index,
266
308
  criteria : iterator.call(context, value, index, list)
267
309
  };
268
310
  }).sort(function(left, right) {
269
- var a = left.criteria, b = right.criteria;
270
- if (a === void 0) return 1;
271
- if (b === void 0) return -1;
272
- return a < b ? -1 : a > b ? 1 : 0;
311
+ var a = left.criteria;
312
+ var b = right.criteria;
313
+ if (a !== b) {
314
+ if (a > b || a === void 0) return 1;
315
+ if (a < b || b === void 0) return -1;
316
+ }
317
+ return left.index < right.index ? -1 : 1;
273
318
  }), 'value');
274
319
  };
275
320
 
276
- // Groups the object's values by a criterion. Pass either a string attribute
277
- // to group by, or a function that returns the criterion.
278
- _.groupBy = function(obj, val) {
321
+ // An internal function used for aggregate "group by" operations.
322
+ var group = function(obj, value, context, behavior) {
279
323
  var result = {};
280
- var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
324
+ var iterator = lookupIterator(value);
281
325
  each(obj, function(value, index) {
282
- var key = iterator(value, index);
283
- (result[key] || (result[key] = [])).push(value);
326
+ var key = iterator.call(context, value, index, obj);
327
+ behavior(result, key, value);
284
328
  });
285
329
  return result;
286
330
  };
287
331
 
288
- // Use a comparator function to figure out at what index an object should
289
- // be inserted so as to maintain order. Uses binary search.
290
- _.sortedIndex = function(array, obj, iterator) {
291
- iterator || (iterator = _.identity);
332
+ // Groups the object's values by a criterion. Pass either a string attribute
333
+ // to group by, or a function that returns the criterion.
334
+ _.groupBy = function(obj, value, context) {
335
+ return group(obj, value, context, function(result, key, value) {
336
+ (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
337
+ });
338
+ };
339
+
340
+ // Counts instances of an object that group by a certain criterion. Pass
341
+ // either a string attribute to count by, or a function that returns the
342
+ // criterion.
343
+ _.countBy = function(obj, value, context) {
344
+ return group(obj, value, context, function(result, key, value) {
345
+ if (!_.has(result, key)) result[key] = 0;
346
+ result[key]++;
347
+ });
348
+ };
349
+
350
+ // Use a comparator function to figure out the smallest index at which
351
+ // an object should be inserted so as to maintain order. Uses binary search.
352
+ _.sortedIndex = function(array, obj, iterator, context) {
353
+ iterator = iterator == null ? _.identity : lookupIterator(iterator);
354
+ var value = iterator.call(context, obj);
292
355
  var low = 0, high = array.length;
293
356
  while (low < high) {
294
- var mid = (low + high) >> 1;
295
- iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
357
+ var mid = (low + high) >>> 1;
358
+ iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
296
359
  }
297
360
  return low;
298
361
  };
299
362
 
300
363
  // Safely convert anything iterable into a real, live array.
301
364
  _.toArray = function(obj) {
302
- if (!obj) return [];
303
- if (_.isArray(obj)) return slice.call(obj);
304
- if (_.isArguments(obj)) return slice.call(obj);
305
- if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray();
365
+ if (!obj) return [];
366
+ if (obj.length === +obj.length) return slice.call(obj);
306
367
  return _.values(obj);
307
368
  };
308
369
 
309
370
  // Return the number of elements in an object.
310
371
  _.size = function(obj) {
311
- return _.isArray(obj) ? obj.length : _.keys(obj).length;
372
+ return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
312
373
  };
313
374
 
314
375
  // Array Functions
@@ -321,7 +382,7 @@
321
382
  return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
322
383
  };
323
384
 
324
- // Returns everything but the last entry of the array. Especcialy useful on
385
+ // Returns everything but the last entry of the array. Especially useful on
325
386
  // the arguments object. Passing **n** will return all the values in
326
387
  // the array, excluding the last N. The **guard** check allows it to work with
327
388
  // `_.map`.
@@ -339,12 +400,12 @@
339
400
  }
340
401
  };
341
402
 
342
- // Returns everything but the first entry of the array. Aliased as `tail`.
343
- // Especially useful on the arguments object. Passing an **index** will return
344
- // the rest of the values in the array from that index onward. The **guard**
403
+ // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
404
+ // Especially useful on the arguments object. Passing an **n** will return
405
+ // the rest N values in the array. The **guard**
345
406
  // check allows it to work with `_.map`.
346
- _.rest = _.tail = function(array, index, guard) {
347
- return slice.call(array, (index == null) || guard ? 1 : index);
407
+ _.rest = _.tail = _.drop = function(array, n, guard) {
408
+ return slice.call(array, (n == null) || guard ? 1 : n);
348
409
  };
349
410
 
350
411
  // Trim out all falsy values from an array.
@@ -352,13 +413,21 @@
352
413
  return _.filter(array, function(value){ return !!value; });
353
414
  };
354
415
 
416
+ // Internal implementation of a recursive `flatten` function.
417
+ var flatten = function(input, shallow, output) {
418
+ each(input, function(value) {
419
+ if (_.isArray(value)) {
420
+ shallow ? push.apply(output, value) : flatten(value, shallow, output);
421
+ } else {
422
+ output.push(value);
423
+ }
424
+ });
425
+ return output;
426
+ };
427
+
355
428
  // Return a completely flattened version of an array.
356
429
  _.flatten = function(array, shallow) {
357
- return _.reduce(array, function(memo, value) {
358
- if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
359
- memo[memo.length] = value;
360
- return memo;
361
- }, []);
430
+ return flatten(array, shallow, []);
362
431
  };
363
432
 
364
433
  // Return a version of the array that does not contain the specified value(s).
@@ -369,30 +438,28 @@
369
438
  // Produce a duplicate-free version of the array. If the array has already
370
439
  // been sorted, you have the option of using a faster algorithm.
371
440
  // Aliased as `unique`.
372
- _.uniq = _.unique = function(array, isSorted, iterator) {
373
- var initial = iterator ? _.map(array, iterator) : array;
441
+ _.uniq = _.unique = function(array, isSorted, iterator, context) {
442
+ var initial = iterator ? _.map(array, iterator, context) : array;
374
443
  var results = [];
375
- // The `isSorted` flag is irrelevant if the array only contains two elements.
376
- if (array.length < 3) isSorted = true;
377
- _.reduce(initial, function (memo, value, index) {
378
- if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
379
- memo.push(value);
444
+ var seen = [];
445
+ each(initial, function(value, index) {
446
+ if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
447
+ seen.push(value);
380
448
  results.push(array[index]);
381
449
  }
382
- return memo;
383
- }, []);
450
+ });
384
451
  return results;
385
452
  };
386
453
 
387
454
  // Produce an array that contains the union: each distinct element from all of
388
455
  // the passed-in arrays.
389
456
  _.union = function() {
390
- return _.uniq(_.flatten(arguments, true));
457
+ return _.uniq(concat.apply(ArrayProto, arguments));
391
458
  };
392
459
 
393
460
  // Produce an array that contains every item shared between all the
394
- // passed-in arrays. (Aliased as "intersect" for back-compat.)
395
- _.intersection = _.intersect = function(array) {
461
+ // passed-in arrays.
462
+ _.intersection = function(array) {
396
463
  var rest = slice.call(arguments, 1);
397
464
  return _.filter(_.uniq(array), function(item) {
398
465
  return _.every(rest, function(other) {
@@ -404,8 +471,8 @@
404
471
  // Take the difference between one array and a number of other arrays.
405
472
  // Only the elements present in just the first array will remain.
406
473
  _.difference = function(array) {
407
- var rest = _.flatten(slice.call(arguments, 1), true);
408
- return _.filter(array, function(value){ return !_.include(rest, value); });
474
+ var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
475
+ return _.filter(array, function(value){ return !_.contains(rest, value); });
409
476
  };
410
477
 
411
478
  // Zip together multiple lists into a single array -- elements that share
@@ -414,10 +481,27 @@
414
481
  var args = slice.call(arguments);
415
482
  var length = _.max(_.pluck(args, 'length'));
416
483
  var results = new Array(length);
417
- for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
484
+ for (var i = 0; i < length; i++) {
485
+ results[i] = _.pluck(args, "" + i);
486
+ }
418
487
  return results;
419
488
  };
420
489
 
490
+ // Converts lists into objects. Pass either a single array of `[key, value]`
491
+ // pairs, or two parallel arrays of the same length -- one of keys, and one of
492
+ // the corresponding values.
493
+ _.object = function(list, values) {
494
+ var result = {};
495
+ for (var i = 0, l = list.length; i < l; i++) {
496
+ if (values) {
497
+ result[list[i]] = values[i];
498
+ } else {
499
+ result[list[i][0]] = list[i][1];
500
+ }
501
+ }
502
+ return result;
503
+ };
504
+
421
505
  // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
422
506
  // we need this function. Return the position of the first occurrence of an
423
507
  // item in an array, or -1 if the item is not included in the array.
@@ -426,22 +510,29 @@
426
510
  // for **isSorted** to use binary search.
427
511
  _.indexOf = function(array, item, isSorted) {
428
512
  if (array == null) return -1;
429
- var i, l;
513
+ var i = 0, l = array.length;
430
514
  if (isSorted) {
431
- i = _.sortedIndex(array, item);
432
- return array[i] === item ? i : -1;
515
+ if (typeof isSorted == 'number') {
516
+ i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
517
+ } else {
518
+ i = _.sortedIndex(array, item);
519
+ return array[i] === item ? i : -1;
520
+ }
433
521
  }
434
- if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
435
- for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
522
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
523
+ for (; i < l; i++) if (array[i] === item) return i;
436
524
  return -1;
437
525
  };
438
526
 
439
527
  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
440
- _.lastIndexOf = function(array, item) {
528
+ _.lastIndexOf = function(array, item, from) {
441
529
  if (array == null) return -1;
442
- if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
443
- var i = array.length;
444
- while (i--) if (i in array && array[i] === item) return i;
530
+ var hasIndex = from != null;
531
+ if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
532
+ return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
533
+ }
534
+ var i = (hasIndex ? from : array.length);
535
+ while (i--) if (array[i] === item) return i;
445
536
  return -1;
446
537
  };
447
538
 
@@ -533,17 +624,19 @@
533
624
  context = this; args = arguments;
534
625
  var later = function() {
535
626
  timeout = null;
536
- if (more) func.apply(context, args);
627
+ if (more) {
628
+ result = func.apply(context, args);
629
+ }
537
630
  whenDone();
538
631
  };
539
632
  if (!timeout) timeout = setTimeout(later, wait);
540
633
  if (throttling) {
541
634
  more = true;
542
635
  } else {
636
+ throttling = true;
543
637
  result = func.apply(context, args);
544
638
  }
545
639
  whenDone();
546
- throttling = true;
547
640
  return result;
548
641
  };
549
642
  };
@@ -553,16 +646,18 @@
553
646
  // N milliseconds. If `immediate` is passed, trigger the function on the
554
647
  // leading edge, instead of the trailing.
555
648
  _.debounce = function(func, wait, immediate) {
556
- var timeout;
649
+ var timeout, result;
557
650
  return function() {
558
651
  var context = this, args = arguments;
559
652
  var later = function() {
560
653
  timeout = null;
561
- if (!immediate) func.apply(context, args);
654
+ if (!immediate) result = func.apply(context, args);
562
655
  };
563
- if (immediate && !timeout) func.apply(context, args);
656
+ var callNow = immediate && !timeout;
564
657
  clearTimeout(timeout);
565
658
  timeout = setTimeout(later, wait);
659
+ if (callNow) result = func.apply(context, args);
660
+ return result;
566
661
  };
567
662
  };
568
663
 
@@ -573,7 +668,9 @@
573
668
  return function() {
574
669
  if (ran) return memo;
575
670
  ran = true;
576
- return memo = func.apply(this, arguments);
671
+ memo = func.apply(this, arguments);
672
+ func = null;
673
+ return memo;
577
674
  };
578
675
  };
579
676
 
@@ -582,7 +679,8 @@
582
679
  // conditionally execute the original function.
583
680
  _.wrap = function(func, wrapper) {
584
681
  return function() {
585
- var args = [func].concat(slice.call(arguments, 0));
682
+ var args = [func];
683
+ push.apply(args, arguments);
586
684
  return wrapper.apply(this, args);
587
685
  };
588
686
  };
@@ -604,7 +702,9 @@
604
702
  _.after = function(times, func) {
605
703
  if (times <= 0) return func();
606
704
  return function() {
607
- if (--times < 1) { return func.apply(this, arguments); }
705
+ if (--times < 1) {
706
+ return func.apply(this, arguments);
707
+ }
608
708
  };
609
709
  };
610
710
 
@@ -622,7 +722,23 @@
622
722
 
623
723
  // Retrieve the values of an object's properties.
624
724
  _.values = function(obj) {
625
- return _.map(obj, _.identity);
725
+ var values = [];
726
+ for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
727
+ return values;
728
+ };
729
+
730
+ // Convert an object into a list of `[key, value]` pairs.
731
+ _.pairs = function(obj) {
732
+ var pairs = [];
733
+ for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
734
+ return pairs;
735
+ };
736
+
737
+ // Invert the keys and values of an object. The values must be serializable.
738
+ _.invert = function(obj) {
739
+ var result = {};
740
+ for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
741
+ return result;
626
742
  };
627
743
 
628
744
  // Return a sorted list of the function names available on the object.
@@ -647,11 +763,22 @@
647
763
 
648
764
  // Return a copy of the object only containing the whitelisted properties.
649
765
  _.pick = function(obj) {
650
- var result = {};
651
- each(_.flatten(slice.call(arguments, 1)), function(key) {
652
- if (key in obj) result[key] = obj[key];
766
+ var copy = {};
767
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
768
+ each(keys, function(key) {
769
+ if (key in obj) copy[key] = obj[key];
653
770
  });
654
- return result;
771
+ return copy;
772
+ };
773
+
774
+ // Return a copy of the object without the blacklisted properties.
775
+ _.omit = function(obj) {
776
+ var copy = {};
777
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
778
+ for (var key in obj) {
779
+ if (!_.contains(keys, key)) copy[key] = obj[key];
780
+ }
781
+ return copy;
655
782
  };
656
783
 
657
784
  // Fill in a given object with default properties.
@@ -678,19 +805,16 @@
678
805
  return obj;
679
806
  };
680
807
 
681
- // Internal recursive comparison function.
682
- function eq(a, b, stack) {
808
+ // Internal recursive comparison function for `isEqual`.
809
+ var eq = function(a, b, aStack, bStack) {
683
810
  // Identical objects are equal. `0 === -0`, but they aren't identical.
684
811
  // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
685
812
  if (a === b) return a !== 0 || 1 / a == 1 / b;
686
813
  // A strict comparison is necessary because `null == undefined`.
687
814
  if (a == null || b == null) return a === b;
688
815
  // Unwrap any wrapped objects.
689
- if (a._chain) a = a._wrapped;
690
- if (b._chain) b = b._wrapped;
691
- // Invoke a custom `isEqual` method if one is provided.
692
- if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
693
- if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
816
+ if (a instanceof _) a = a._wrapped;
817
+ if (b instanceof _) b = b._wrapped;
694
818
  // Compare `[[Class]]` names.
695
819
  var className = toString.call(a);
696
820
  if (className != toString.call(b)) return false;
@@ -720,14 +844,15 @@
720
844
  if (typeof a != 'object' || typeof b != 'object') return false;
721
845
  // Assume equality for cyclic structures. The algorithm for detecting cyclic
722
846
  // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
723
- var length = stack.length;
847
+ var length = aStack.length;
724
848
  while (length--) {
725
849
  // Linear search. Performance is inversely proportional to the number of
726
850
  // unique nested structures.
727
- if (stack[length] == a) return true;
851
+ if (aStack[length] == a) return bStack[length] == b;
728
852
  }
729
853
  // Add the first object to the stack of traversed objects.
730
- stack.push(a);
854
+ aStack.push(a);
855
+ bStack.push(b);
731
856
  var size = 0, result = true;
732
857
  // Recursively compare objects and arrays.
733
858
  if (className == '[object Array]') {
@@ -737,20 +862,24 @@
737
862
  if (result) {
738
863
  // Deep compare the contents, ignoring non-numeric properties.
739
864
  while (size--) {
740
- // Ensure commutative equality for sparse arrays.
741
- if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
865
+ if (!(result = eq(a[size], b[size], aStack, bStack))) break;
742
866
  }
743
867
  }
744
868
  } else {
745
- // Objects with different constructors are not equivalent.
746
- if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
869
+ // Objects with different constructors are not equivalent, but `Object`s
870
+ // from different frames are.
871
+ var aCtor = a.constructor, bCtor = b.constructor;
872
+ if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
873
+ _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
874
+ return false;
875
+ }
747
876
  // Deep compare objects.
748
877
  for (var key in a) {
749
878
  if (_.has(a, key)) {
750
879
  // Count the expected number of properties.
751
880
  size++;
752
881
  // Deep compare each member.
753
- if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
882
+ if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
754
883
  }
755
884
  }
756
885
  // Ensure that both objects contain the same number of properties.
@@ -762,13 +891,14 @@
762
891
  }
763
892
  }
764
893
  // Remove the first object from the stack of traversed objects.
765
- stack.pop();
894
+ aStack.pop();
895
+ bStack.pop();
766
896
  return result;
767
- }
897
+ };
768
898
 
769
899
  // Perform a deep comparison to check if two objects are equal.
770
900
  _.isEqual = function(a, b) {
771
- return eq(a, b, []);
901
+ return eq(a, b, [], []);
772
902
  };
773
903
 
774
904
  // Is a given array, string, or object empty?
@@ -782,7 +912,7 @@
782
912
 
783
913
  // Is a given value a DOM element?
784
914
  _.isElement = function(obj) {
785
- return !!(obj && obj.nodeType == 1);
915
+ return !!(obj && obj.nodeType === 1);
786
916
  };
787
917
 
788
918
  // Is a given value an array?
@@ -796,40 +926,36 @@
796
926
  return obj === Object(obj);
797
927
  };
798
928
 
799
- // Is a given variable an arguments object?
800
- _.isArguments = function(obj) {
801
- return toString.call(obj) == '[object Arguments]';
802
- };
929
+ // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
930
+ each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
931
+ _['is' + name] = function(obj) {
932
+ return toString.call(obj) == '[object ' + name + ']';
933
+ };
934
+ });
935
+
936
+ // Define a fallback version of the method in browsers (ahem, IE), where
937
+ // there isn't any inspectable "Arguments" type.
803
938
  if (!_.isArguments(arguments)) {
804
939
  _.isArguments = function(obj) {
805
940
  return !!(obj && _.has(obj, 'callee'));
806
941
  };
807
942
  }
808
943
 
809
- // Is a given value a function?
810
- _.isFunction = function(obj) {
811
- return toString.call(obj) == '[object Function]';
812
- };
813
-
814
- // Is a given value a string?
815
- _.isString = function(obj) {
816
- return toString.call(obj) == '[object String]';
817
- };
818
-
819
- // Is a given value a number?
820
- _.isNumber = function(obj) {
821
- return toString.call(obj) == '[object Number]';
822
- };
944
+ // Optimize `isFunction` if appropriate.
945
+ if (typeof (/./) !== 'function') {
946
+ _.isFunction = function(obj) {
947
+ return typeof obj === 'function';
948
+ };
949
+ }
823
950
 
824
951
  // Is a given object a finite number?
825
952
  _.isFinite = function(obj) {
826
953
  return _.isNumber(obj) && isFinite(obj);
827
954
  };
828
955
 
829
- // Is the given value `NaN`?
956
+ // Is the given value `NaN`? (NaN is the only number which does not equal itself).
830
957
  _.isNaN = function(obj) {
831
- // `NaN` is the only value for which `===` is not reflexive.
832
- return obj !== obj;
958
+ return _.isNumber(obj) && obj != +obj;
833
959
  };
834
960
 
835
961
  // Is a given value a boolean?
@@ -837,16 +963,6 @@
837
963
  return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
838
964
  };
839
965
 
840
- // Is a given value a date?
841
- _.isDate = function(obj) {
842
- return toString.call(obj) == '[object Date]';
843
- };
844
-
845
- // Is the given value a regular expression?
846
- _.isRegExp = function(obj) {
847
- return toString.call(obj) == '[object RegExp]';
848
- };
849
-
850
966
  // Is a given value equal to null?
851
967
  _.isNull = function(obj) {
852
968
  return obj === null;
@@ -857,7 +973,8 @@
857
973
  return obj === void 0;
858
974
  };
859
975
 
860
- // Has own property?
976
+ // Shortcut function for checking if an object has a given property directly
977
+ // on itself (in other words, not on a prototype).
861
978
  _.has = function(obj, key) {
862
979
  return hasOwnProperty.call(obj, key);
863
980
  };
@@ -878,14 +995,47 @@
878
995
  };
879
996
 
880
997
  // Run a function **n** times.
881
- _.times = function (n, iterator, context) {
998
+ _.times = function(n, iterator, context) {
882
999
  for (var i = 0; i < n; i++) iterator.call(context, i);
883
1000
  };
884
1001
 
885
- // Escape a string for HTML interpolation.
886
- _.escape = function(string) {
887
- return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
1002
+ // Return a random integer between min and max (inclusive).
1003
+ _.random = function(min, max) {
1004
+ if (max == null) {
1005
+ max = min;
1006
+ min = 0;
1007
+ }
1008
+ return min + (0 | Math.random() * (max - min + 1));
1009
+ };
1010
+
1011
+ // List of HTML entities for escaping.
1012
+ var entityMap = {
1013
+ escape: {
1014
+ '&': '&amp;',
1015
+ '<': '&lt;',
1016
+ '>': '&gt;',
1017
+ '"': '&quot;',
1018
+ "'": '&#x27;',
1019
+ '/': '&#x2F;'
1020
+ }
888
1021
  };
1022
+ entityMap.unescape = _.invert(entityMap.escape);
1023
+
1024
+ // Regexes containing the keys and values listed immediately above.
1025
+ var entityRegexes = {
1026
+ escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
1027
+ unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
1028
+ };
1029
+
1030
+ // Functions for escaping and unescaping strings to/from HTML interpolation.
1031
+ _.each(['escape', 'unescape'], function(method) {
1032
+ _[method] = function(string) {
1033
+ if (string == null) return '';
1034
+ return ('' + string).replace(entityRegexes[method], function(match) {
1035
+ return entityMap[method][match];
1036
+ });
1037
+ };
1038
+ });
889
1039
 
890
1040
  // If the value of the named property is a function then invoke it;
891
1041
  // otherwise, return it.
@@ -895,11 +1045,15 @@
895
1045
  return _.isFunction(value) ? value.call(object) : value;
896
1046
  };
897
1047
 
898
- // Add your own custom functions to the Underscore object, ensuring that
899
- // they're correctly added to the OOP wrapper as well.
1048
+ // Add your own custom functions to the Underscore object.
900
1049
  _.mixin = function(obj) {
901
1050
  each(_.functions(obj), function(name){
902
- addToWrapper(name, _[name] = obj[name]);
1051
+ var func = _[name] = obj[name];
1052
+ _.prototype[name] = function() {
1053
+ var args = [this._wrapped];
1054
+ push.apply(args, arguments);
1055
+ return result.call(this, func.apply(_, args));
1056
+ };
903
1057
  });
904
1058
  };
905
1059
 
@@ -922,72 +1076,70 @@
922
1076
  // When customizing `templateSettings`, if you don't want to define an
923
1077
  // interpolation, evaluation or escaping regex, we need one that is
924
1078
  // guaranteed not to match.
925
- var noMatch = /.^/;
1079
+ var noMatch = /(.)^/;
926
1080
 
927
1081
  // Certain characters need to be escaped so that they can be put into a
928
1082
  // string literal.
929
1083
  var escapes = {
930
- '\\': '\\',
931
- "'": "'",
932
- 'r': '\r',
933
- 'n': '\n',
934
- 't': '\t',
935
- 'u2028': '\u2028',
936
- 'u2029': '\u2029'
1084
+ "'": "'",
1085
+ '\\': '\\',
1086
+ '\r': 'r',
1087
+ '\n': 'n',
1088
+ '\t': 't',
1089
+ '\u2028': 'u2028',
1090
+ '\u2029': 'u2029'
937
1091
  };
938
1092
 
939
- for (var p in escapes) escapes[escapes[p]] = p;
940
1093
  var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
941
- var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
942
-
943
- // Within an interpolation, evaluation, or escaping, remove HTML escaping
944
- // that had been previously added.
945
- var unescape = function(code) {
946
- return code.replace(unescaper, function(match, escape) {
947
- return escapes[escape];
948
- });
949
- };
950
1094
 
951
1095
  // JavaScript micro-templating, similar to John Resig's implementation.
952
1096
  // Underscore templating handles arbitrary delimiters, preserves whitespace,
953
1097
  // and correctly escapes quotes within interpolated code.
954
1098
  _.template = function(text, data, settings) {
955
- settings = _.defaults(settings || {}, _.templateSettings);
956
-
957
- // Compile the template source, taking care to escape characters that
958
- // cannot be included in a string literal and then unescape them in code
959
- // blocks.
960
- var source = "__p+='" + text
961
- .replace(escaper, function(match) {
962
- return '\\' + escapes[match];
963
- })
964
- .replace(settings.escape || noMatch, function(match, code) {
965
- return "'+\n_.escape(" + unescape(code) + ")+\n'";
966
- })
967
- .replace(settings.interpolate || noMatch, function(match, code) {
968
- return "'+\n(" + unescape(code) + ")+\n'";
969
- })
970
- .replace(settings.evaluate || noMatch, function(match, code) {
971
- return "';\n" + unescape(code) + "\n;__p+='";
972
- }) + "';\n";
1099
+ settings = _.defaults({}, settings, _.templateSettings);
1100
+
1101
+ // Combine delimiters into one regular expression via alternation.
1102
+ var matcher = new RegExp([
1103
+ (settings.escape || noMatch).source,
1104
+ (settings.interpolate || noMatch).source,
1105
+ (settings.evaluate || noMatch).source
1106
+ ].join('|') + '|$', 'g');
1107
+
1108
+ // Compile the template source, escaping string literals appropriately.
1109
+ var index = 0;
1110
+ var source = "__p+='";
1111
+ text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
1112
+ source += text.slice(index, offset)
1113
+ .replace(escaper, function(match) { return '\\' + escapes[match]; });
1114
+ source +=
1115
+ escape ? "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" :
1116
+ interpolate ? "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" :
1117
+ evaluate ? "';\n" + evaluate + "\n__p+='" : '';
1118
+ index = offset + match.length;
1119
+ });
1120
+ source += "';\n";
973
1121
 
974
1122
  // If a variable is not specified, place data values in local scope.
975
1123
  if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
976
1124
 
977
- source = "var __p='';" +
978
- "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" +
1125
+ source = "var __t,__p='',__j=Array.prototype.join," +
1126
+ "print=function(){__p+=__j.call(arguments,'');};\n" +
979
1127
  source + "return __p;\n";
980
1128
 
981
- var render = new Function(settings.variable || 'obj', '_', source);
1129
+ try {
1130
+ var render = new Function(settings.variable || 'obj', '_', source);
1131
+ } catch (e) {
1132
+ e.source = source;
1133
+ throw e;
1134
+ }
1135
+
982
1136
  if (data) return render(data, _);
983
1137
  var template = function(data) {
984
1138
  return render.call(this, data, _);
985
1139
  };
986
1140
 
987
- // Provide the compiled function source as a convenience for build time
988
- // precompilation.
989
- template.source = 'function(' + (settings.variable || 'obj') + '){\n' +
990
- source + '}';
1141
+ // Provide the compiled function source as a convenience for precompilation.
1142
+ template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
991
1143
 
992
1144
  return template;
993
1145
  };
@@ -997,29 +1149,15 @@
997
1149
  return _(obj).chain();
998
1150
  };
999
1151
 
1000
- // The OOP Wrapper
1152
+ // OOP
1001
1153
  // ---------------
1002
-
1003
1154
  // If Underscore is called as a function, it returns a wrapped object that
1004
1155
  // can be used OO-style. This wrapper holds altered versions of all the
1005
1156
  // underscore functions. Wrapped objects may be chained.
1006
- var wrapper = function(obj) { this._wrapped = obj; };
1007
-
1008
- // Expose `wrapper.prototype` as `_.prototype`
1009
- _.prototype = wrapper.prototype;
1010
1157
 
1011
1158
  // Helper function to continue chaining intermediate results.
1012
- var result = function(obj, chain) {
1013
- return chain ? _(obj).chain() : obj;
1014
- };
1015
-
1016
- // A method to easily add functions to the OOP wrapper.
1017
- var addToWrapper = function(name, func) {
1018
- wrapper.prototype[name] = function() {
1019
- var args = slice.call(arguments);
1020
- unshift.call(args, this._wrapped);
1021
- return result(func.apply(_, args), this._chain);
1022
- };
1159
+ var result = function(obj) {
1160
+ return this._chain ? _(obj).chain() : obj;
1023
1161
  };
1024
1162
 
1025
1163
  // Add all of the Underscore functions to the wrapper object.
@@ -1028,32 +1166,35 @@
1028
1166
  // Add all mutator Array functions to the wrapper.
1029
1167
  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
1030
1168
  var method = ArrayProto[name];
1031
- wrapper.prototype[name] = function() {
1032
- var wrapped = this._wrapped;
1033
- method.apply(wrapped, arguments);
1034
- var length = wrapped.length;
1035
- if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
1036
- return result(wrapped, this._chain);
1169
+ _.prototype[name] = function() {
1170
+ var obj = this._wrapped;
1171
+ method.apply(obj, arguments);
1172
+ if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
1173
+ return result.call(this, obj);
1037
1174
  };
1038
1175
  });
1039
1176
 
1040
1177
  // Add all accessor Array functions to the wrapper.
1041
1178
  each(['concat', 'join', 'slice'], function(name) {
1042
1179
  var method = ArrayProto[name];
1043
- wrapper.prototype[name] = function() {
1044
- return result(method.apply(this._wrapped, arguments), this._chain);
1180
+ _.prototype[name] = function() {
1181
+ return result.call(this, method.apply(this._wrapped, arguments));
1045
1182
  };
1046
1183
  });
1047
1184
 
1048
- // Start chaining a wrapped Underscore object.
1049
- wrapper.prototype.chain = function() {
1050
- this._chain = true;
1051
- return this;
1052
- };
1185
+ _.extend(_.prototype, {
1053
1186
 
1054
- // Extracts the result from a wrapped and chained object.
1055
- wrapper.prototype.value = function() {
1056
- return this._wrapped;
1057
- };
1187
+ // Start chaining a wrapped Underscore object.
1188
+ chain: function() {
1189
+ this._chain = true;
1190
+ return this;
1191
+ },
1192
+
1193
+ // Extracts the result from a wrapped and chained object.
1194
+ value: function() {
1195
+ return this._wrapped;
1196
+ }
1197
+
1198
+ });
1058
1199
 
1059
- }).call(this);
1200
+ }).call(this);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: underscore_extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-26 00:00:00.000000000 Z
12
+ date: 2012-10-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fuubar
@@ -145,21 +145,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
145
145
  - - ! '>='
146
146
  - !ruby/object:Gem::Version
147
147
  version: '0'
148
- segments:
149
- - 0
150
- hash: -3788224684099752220
151
148
  required_rubygems_version: !ruby/object:Gem::Requirement
152
149
  none: false
153
150
  requirements:
154
151
  - - ! '>='
155
152
  - !ruby/object:Gem::Version
156
153
  version: '0'
157
- segments:
158
- - 0
159
- hash: -3788224684099752220
160
154
  requirements: []
161
155
  rubyforge_project: underscore_extensions
162
- rubygems_version: 1.8.24
156
+ rubygems_version: 1.8.19
163
157
  signing_key:
164
158
  specification_version: 3
165
159
  summary: Extensions to underscore javascript library as a rails engine