underscore_extensions 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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