frank-cucumber 0.9.6 → 0.9.7

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.
Files changed (50) hide show
  1. data/frank-skeleton/frank_static_resources.bundle/index.html +29 -34
  2. data/frank-skeleton/frank_static_resources.bundle/{index.haml → index.html.haml} +28 -29
  3. data/frank-skeleton/frank_static_resources.bundle/js/accessible_views_view.coffee +41 -0
  4. data/frank-skeleton/frank_static_resources.bundle/js/accessible_views_view.js +46 -0
  5. data/frank-skeleton/frank_static_resources.bundle/js/controller.coffee +129 -0
  6. data/frank-skeleton/frank_static_resources.bundle/js/controller.js +142 -0
  7. data/frank-skeleton/frank_static_resources.bundle/js/details_view.coffee +42 -0
  8. data/frank-skeleton/frank_static_resources.bundle/js/details_view.js +51 -0
  9. data/frank-skeleton/frank_static_resources.bundle/js/dropdown_control.coffee +64 -0
  10. data/frank-skeleton/frank_static_resources.bundle/js/dropdown_control.js +73 -0
  11. data/frank-skeleton/frank_static_resources.bundle/js/ersatz_model.coffee +46 -0
  12. data/frank-skeleton/frank_static_resources.bundle/js/ersatz_model.js +59 -0
  13. data/frank-skeleton/frank_static_resources.bundle/js/ersatz_view.coffee +167 -0
  14. data/frank-skeleton/frank_static_resources.bundle/js/ersatz_view.js +198 -0
  15. data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_model.coffee +10 -0
  16. data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_model.js +17 -0
  17. data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_view.coffee +43 -0
  18. data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_view.js +60 -0
  19. data/frank-skeleton/frank_static_resources.bundle/js/frank.coffee +96 -0
  20. data/frank-skeleton/frank_static_resources.bundle/js/frank.js +118 -0
  21. data/frank-skeleton/frank_static_resources.bundle/js/lib/backbone.js +1431 -0
  22. data/frank-skeleton/frank_static_resources.bundle/{coffee-script.js → js/lib/coffee-script.js} +0 -0
  23. data/frank-skeleton/frank_static_resources.bundle/{jquery-ui.min.js → js/lib/jquery-ui.min.js} +0 -0
  24. data/frank-skeleton/frank_static_resources.bundle/{jquery.min.js → js/lib/jquery.min.js} +0 -0
  25. data/frank-skeleton/frank_static_resources.bundle/{jquery.treeview.js → js/lib/jquery.treeview.js} +0 -0
  26. data/frank-skeleton/frank_static_resources.bundle/{json2.js → js/lib/json2.js} +0 -0
  27. data/frank-skeleton/frank_static_resources.bundle/js/lib/raphael.js +5815 -0
  28. data/frank-skeleton/frank_static_resources.bundle/js/lib/require.js +2053 -0
  29. data/frank-skeleton/frank_static_resources.bundle/{underscore.js → js/lib/underscore.js} +466 -177
  30. data/frank-skeleton/frank_static_resources.bundle/js/main.coffee +27 -0
  31. data/frank-skeleton/frank_static_resources.bundle/js/main.js +29 -0
  32. data/frank-skeleton/frank_static_resources.bundle/js/tabs_controller.coffee +13 -0
  33. data/frank-skeleton/frank_static_resources.bundle/js/tabs_controller.js +22 -0
  34. data/frank-skeleton/frank_static_resources.bundle/js/toast_controller.coffee +15 -0
  35. data/frank-skeleton/frank_static_resources.bundle/js/toast_controller.js +28 -0
  36. data/frank-skeleton/frank_static_resources.bundle/js/transform_stack.coffee +59 -0
  37. data/frank-skeleton/frank_static_resources.bundle/js/transform_stack.js +78 -0
  38. data/frank-skeleton/frank_static_resources.bundle/js/tree_view.coffee +53 -0
  39. data/frank-skeleton/frank_static_resources.bundle/js/tree_view.js +64 -0
  40. data/frank-skeleton/frank_static_resources.bundle/js/view_heir_model.coffee +37 -0
  41. data/frank-skeleton/frank_static_resources.bundle/js/view_heir_model.js +48 -0
  42. data/frank-skeleton/frank_static_resources.bundle/js/view_model.coffee +39 -0
  43. data/frank-skeleton/frank_static_resources.bundle/js/view_model.js +62 -0
  44. data/frank-skeleton/frank_static_resources.bundle/symbiote.css +116 -84
  45. data/lib/frank-cucumber/frankifier.rb +20 -2
  46. data/lib/frank-cucumber/version.rb +1 -1
  47. metadata +70 -38
  48. data/frank-skeleton/frank_static_resources.bundle/raphael-min.js +0 -7
  49. data/frank-skeleton/frank_static_resources.bundle/symbiote.js +0 -585
  50. data/frank-skeleton/frank_static_resources.bundle/symbiote_ui.coffee +0 -39
@@ -1,6 +1,6 @@
1
- // Underscore.js 1.1.4
2
- // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
3
- // Underscore is freely distributable under the MIT license.
1
+ // Underscore.js 1.3.3
2
+ // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
3
+ // Underscore may be freely distributed under the MIT license.
4
4
  // Portions of Underscore are inspired or borrowed from Prototype,
5
5
  // Oliver Steele's Functional, and John Resig's Micro-Templating.
6
6
  // For all details and documentation:
@@ -21,7 +21,7 @@
21
21
  var breaker = {};
22
22
 
23
23
  // Save bytes in the minified (but not gzipped) version:
24
- var ArrayProto = Array.prototype, ObjProto = Object.prototype;
24
+ var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
25
25
 
26
26
  // Create quick reference variables for speed access to core prototypes.
27
27
  var slice = ArrayProto.slice,
@@ -42,42 +42,45 @@
42
42
  nativeIndexOf = ArrayProto.indexOf,
43
43
  nativeLastIndexOf = ArrayProto.lastIndexOf,
44
44
  nativeIsArray = Array.isArray,
45
- nativeKeys = Object.keys;
45
+ nativeKeys = Object.keys,
46
+ nativeBind = FuncProto.bind;
46
47
 
47
48
  // Create a safe reference to the Underscore object for use below.
48
49
  var _ = function(obj) { return new wrapper(obj); };
49
50
 
50
- // Export the Underscore object for **CommonJS**, with backwards-compatibility
51
- // for the old `require()` API. If we're not in CommonJS, add `_` to the
52
- // global object.
53
- if (typeof module !== 'undefined' && module.exports) {
54
- module.exports = _;
55
- _._ = _;
51
+ // Export the Underscore object for **Node.js**, with
52
+ // backwards-compatibility for the old `require()` API. If we're in
53
+ // the browser, add `_` as a global object via a string identifier,
54
+ // for Closure Compiler "advanced" mode.
55
+ if (typeof exports !== 'undefined') {
56
+ if (typeof module !== 'undefined' && module.exports) {
57
+ exports = module.exports = _;
58
+ }
59
+ exports._ = _;
56
60
  } else {
57
- root._ = _;
61
+ root['_'] = _;
58
62
  }
59
63
 
60
64
  // Current version.
61
- _.VERSION = '1.1.4';
65
+ _.VERSION = '1.3.3';
62
66
 
63
67
  // Collection Functions
64
68
  // --------------------
65
69
 
66
70
  // The cornerstone, an `each` implementation, aka `forEach`.
67
- // Handles objects implementing `forEach`, arrays, and raw objects.
71
+ // Handles objects with the built-in `forEach`, arrays, and raw objects.
68
72
  // Delegates to **ECMAScript 5**'s native `forEach` if available.
69
73
  var each = _.each = _.forEach = function(obj, iterator, context) {
70
- var value;
71
74
  if (obj == null) return;
72
75
  if (nativeForEach && obj.forEach === nativeForEach) {
73
76
  obj.forEach(iterator, context);
74
- } else if (_.isNumber(obj.length)) {
77
+ } else if (obj.length === +obj.length) {
75
78
  for (var i = 0, l = obj.length; i < l; i++) {
76
- if (iterator.call(context, obj[i], i, obj) === breaker) return;
79
+ if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
77
80
  }
78
81
  } else {
79
82
  for (var key in obj) {
80
- if (hasOwnProperty.call(obj, key)) {
83
+ if (_.has(obj, key)) {
81
84
  if (iterator.call(context, obj[key], key, obj) === breaker) return;
82
85
  }
83
86
  }
@@ -86,47 +89,50 @@
86
89
 
87
90
  // Return the results of applying the iterator to each element.
88
91
  // Delegates to **ECMAScript 5**'s native `map` if available.
89
- _.map = function(obj, iterator, context) {
92
+ _.map = _.collect = function(obj, iterator, context) {
90
93
  var results = [];
91
94
  if (obj == null) return results;
92
95
  if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
93
96
  each(obj, function(value, index, list) {
94
97
  results[results.length] = iterator.call(context, value, index, list);
95
98
  });
99
+ if (obj.length === +obj.length) results.length = obj.length;
96
100
  return results;
97
101
  };
98
102
 
99
103
  // **Reduce** builds up a single result from a list of values, aka `inject`,
100
104
  // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
101
105
  _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
102
- var initial = memo !== void 0;
106
+ var initial = arguments.length > 2;
103
107
  if (obj == null) obj = [];
104
108
  if (nativeReduce && obj.reduce === nativeReduce) {
105
109
  if (context) iterator = _.bind(iterator, context);
106
110
  return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
107
111
  }
108
112
  each(obj, function(value, index, list) {
109
- if (!initial && index === 0) {
113
+ if (!initial) {
110
114
  memo = value;
111
115
  initial = true;
112
116
  } else {
113
117
  memo = iterator.call(context, memo, value, index, list);
114
118
  }
115
119
  });
116
- if (!initial) throw new TypeError("Reduce of empty array with no initial value");
120
+ if (!initial) throw new TypeError('Reduce of empty array with no initial value');
117
121
  return memo;
118
122
  };
119
123
 
120
124
  // The right-associative version of reduce, also known as `foldr`.
121
125
  // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
122
126
  _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
127
+ var initial = arguments.length > 2;
123
128
  if (obj == null) obj = [];
124
129
  if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
125
130
  if (context) iterator = _.bind(iterator, context);
126
- return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
131
+ return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
127
132
  }
128
- var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse();
129
- return _.reduce(reversed, iterator, memo, context);
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);
130
136
  };
131
137
 
132
138
  // Return the first value which passes a truth test. Aliased as `detect`.
@@ -168,28 +174,27 @@
168
174
  // Delegates to **ECMAScript 5**'s native `every` if available.
169
175
  // Aliased as `all`.
170
176
  _.every = _.all = function(obj, iterator, context) {
171
- iterator = iterator || _.identity;
172
177
  var result = true;
173
178
  if (obj == null) return result;
174
179
  if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
175
180
  each(obj, function(value, index, list) {
176
181
  if (!(result = result && iterator.call(context, value, index, list))) return breaker;
177
182
  });
178
- return result;
183
+ return !!result;
179
184
  };
180
185
 
181
186
  // Determine if at least one element in the object matches a truth test.
182
187
  // Delegates to **ECMAScript 5**'s native `some` if available.
183
188
  // Aliased as `any`.
184
189
  var any = _.some = _.any = function(obj, iterator, context) {
185
- iterator = iterator || _.identity;
190
+ iterator || (iterator = _.identity);
186
191
  var result = false;
187
192
  if (obj == null) return result;
188
193
  if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
189
194
  each(obj, function(value, index, list) {
190
- if (result = iterator.call(context, value, index, list)) return breaker;
195
+ if (result || (result = iterator.call(context, value, index, list))) return breaker;
191
196
  });
192
- return result;
197
+ return !!result;
193
198
  };
194
199
 
195
200
  // Determine if a given value is included in the array or object using `===`.
@@ -198,8 +203,8 @@
198
203
  var found = false;
199
204
  if (obj == null) return found;
200
205
  if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
201
- any(obj, function(value) {
202
- if (found = value === target) return true;
206
+ found = any(obj, function(value) {
207
+ return value === target;
203
208
  });
204
209
  return found;
205
210
  };
@@ -208,7 +213,7 @@
208
213
  _.invoke = function(obj, method) {
209
214
  var args = slice.call(arguments, 2);
210
215
  return _.map(obj, function(value) {
211
- return (method ? value[method] : value).apply(value, args);
216
+ return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
212
217
  });
213
218
  };
214
219
 
@@ -219,7 +224,8 @@
219
224
 
220
225
  // Return the maximum element or (element-based computation).
221
226
  _.max = function(obj, iterator, context) {
222
- if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
227
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj);
228
+ if (!iterator && _.isEmpty(obj)) return -Infinity;
223
229
  var result = {computed : -Infinity};
224
230
  each(obj, function(value, index, list) {
225
231
  var computed = iterator ? iterator.call(context, value, index, list) : value;
@@ -230,7 +236,8 @@
230
236
 
231
237
  // Return the minimum element (or element-based computation).
232
238
  _.min = function(obj, iterator, context) {
233
- if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
239
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj);
240
+ if (!iterator && _.isEmpty(obj)) return Infinity;
234
241
  var result = {computed : Infinity};
235
242
  each(obj, function(value, index, list) {
236
243
  var computed = iterator ? iterator.call(context, value, index, list) : value;
@@ -239,8 +246,20 @@
239
246
  return result.value;
240
247
  };
241
248
 
249
+ // Shuffle an array.
250
+ _.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];
255
+ shuffled[rand] = value;
256
+ });
257
+ return shuffled;
258
+ };
259
+
242
260
  // Sort the object's values by a criterion produced by an iterator.
243
- _.sortBy = function(obj, iterator, context) {
261
+ _.sortBy = function(obj, val, context) {
262
+ var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
244
263
  return _.pluck(_.map(obj, function(value, index, list) {
245
264
  return {
246
265
  value : value,
@@ -248,14 +267,28 @@
248
267
  };
249
268
  }).sort(function(left, right) {
250
269
  var a = left.criteria, b = right.criteria;
270
+ if (a === void 0) return 1;
271
+ if (b === void 0) return -1;
251
272
  return a < b ? -1 : a > b ? 1 : 0;
252
273
  }), 'value');
253
274
  };
254
275
 
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) {
279
+ var result = {};
280
+ var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
281
+ each(obj, function(value, index) {
282
+ var key = iterator(value, index);
283
+ (result[key] || (result[key] = [])).push(value);
284
+ });
285
+ return result;
286
+ };
287
+
255
288
  // Use a comparator function to figure out at what index an object should
256
289
  // be inserted so as to maintain order. Uses binary search.
257
290
  _.sortedIndex = function(array, obj, iterator) {
258
- iterator = iterator || _.identity;
291
+ iterator || (iterator = _.identity);
259
292
  var low = 0, high = array.length;
260
293
  while (low < high) {
261
294
  var mid = (low + high) >> 1;
@@ -265,27 +298,45 @@
265
298
  };
266
299
 
267
300
  // Safely convert anything iterable into a real, live array.
268
- _.toArray = function(iterable) {
269
- if (!iterable) return [];
270
- if (iterable.toArray) return iterable.toArray();
271
- if (_.isArray(iterable)) return iterable;
272
- if (_.isArguments(iterable)) return slice.call(iterable);
273
- return _.values(iterable);
301
+ _.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();
306
+ return _.values(obj);
274
307
  };
275
308
 
276
309
  // Return the number of elements in an object.
277
310
  _.size = function(obj) {
278
- return _.toArray(obj).length;
311
+ return _.isArray(obj) ? obj.length : _.keys(obj).length;
279
312
  };
280
313
 
281
314
  // Array Functions
282
315
  // ---------------
283
316
 
284
317
  // Get the first element of an array. Passing **n** will return the first N
285
- // values in the array. Aliased as `head`. The **guard** check allows it to work
286
- // with `_.map`.
287
- _.first = _.head = function(array, n, guard) {
288
- return n && !guard ? slice.call(array, 0, n) : array[0];
318
+ // values in the array. Aliased as `head` and `take`. The **guard** check
319
+ // allows it to work with `_.map`.
320
+ _.first = _.head = _.take = function(array, n, guard) {
321
+ return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
322
+ };
323
+
324
+ // Returns everything but the last entry of the array. Especcialy useful on
325
+ // the arguments object. Passing **n** will return all the values in
326
+ // the array, excluding the last N. The **guard** check allows it to work with
327
+ // `_.map`.
328
+ _.initial = function(array, n, guard) {
329
+ return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
330
+ };
331
+
332
+ // Get the last element of an array. Passing **n** will return the last N
333
+ // values in the array. The **guard** check allows it to work with `_.map`.
334
+ _.last = function(array, n, guard) {
335
+ if ((n != null) && !guard) {
336
+ return slice.call(array, Math.max(array.length - n, 0));
337
+ } else {
338
+ return array[array.length - 1];
339
+ }
289
340
  };
290
341
 
291
342
  // Returns everything but the first entry of the array. Aliased as `tail`.
@@ -293,12 +344,7 @@
293
344
  // the rest of the values in the array from that index onward. The **guard**
294
345
  // check allows it to work with `_.map`.
295
346
  _.rest = _.tail = function(array, index, guard) {
296
- return slice.call(array, _.isUndefined(index) || guard ? 1 : index);
297
- };
298
-
299
- // Get the last element of an array.
300
- _.last = function(array) {
301
- return array[array.length - 1];
347
+ return slice.call(array, (index == null) || guard ? 1 : index);
302
348
  };
303
349
 
304
350
  // Trim out all falsy values from an array.
@@ -307,9 +353,9 @@
307
353
  };
308
354
 
309
355
  // Return a completely flattened version of an array.
310
- _.flatten = function(array) {
356
+ _.flatten = function(array, shallow) {
311
357
  return _.reduce(array, function(memo, value) {
312
- if (_.isArray(value)) return memo.concat(_.flatten(value));
358
+ if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
313
359
  memo[memo.length] = value;
314
360
  return memo;
315
361
  }, []);
@@ -317,23 +363,36 @@
317
363
 
318
364
  // Return a version of the array that does not contain the specified value(s).
319
365
  _.without = function(array) {
320
- var values = slice.call(arguments, 1);
321
- return _.filter(array, function(value){ return !_.include(values, value); });
366
+ return _.difference(array, slice.call(arguments, 1));
322
367
  };
323
368
 
324
369
  // Produce a duplicate-free version of the array. If the array has already
325
370
  // been sorted, you have the option of using a faster algorithm.
326
371
  // Aliased as `unique`.
327
- _.uniq = _.unique = function(array, isSorted) {
328
- return _.reduce(array, function(memo, el, i) {
329
- if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el;
372
+ _.uniq = _.unique = function(array, isSorted, iterator) {
373
+ var initial = iterator ? _.map(array, iterator) : array;
374
+ 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);
380
+ results.push(array[index]);
381
+ }
330
382
  return memo;
331
383
  }, []);
384
+ return results;
385
+ };
386
+
387
+ // Produce an array that contains the union: each distinct element from all of
388
+ // the passed-in arrays.
389
+ _.union = function() {
390
+ return _.uniq(_.flatten(arguments, true));
332
391
  };
333
392
 
334
393
  // Produce an array that contains every item shared between all the
335
- // passed-in arrays.
336
- _.intersect = function(array) {
394
+ // passed-in arrays. (Aliased as "intersect" for back-compat.)
395
+ _.intersection = _.intersect = function(array) {
337
396
  var rest = slice.call(arguments, 1);
338
397
  return _.filter(_.uniq(array), function(item) {
339
398
  return _.every(rest, function(other) {
@@ -342,6 +401,13 @@
342
401
  });
343
402
  };
344
403
 
404
+ // Take the difference between one array and a number of other arrays.
405
+ // Only the elements present in just the first array will remain.
406
+ _.difference = function(array) {
407
+ var rest = _.flatten(slice.call(arguments, 1), true);
408
+ return _.filter(array, function(value){ return !_.include(rest, value); });
409
+ };
410
+
345
411
  // Zip together multiple lists into a single array -- elements that share
346
412
  // an index go together.
347
413
  _.zip = function() {
@@ -360,22 +426,22 @@
360
426
  // for **isSorted** to use binary search.
361
427
  _.indexOf = function(array, item, isSorted) {
362
428
  if (array == null) return -1;
429
+ var i, l;
363
430
  if (isSorted) {
364
- var i = _.sortedIndex(array, item);
431
+ i = _.sortedIndex(array, item);
365
432
  return array[i] === item ? i : -1;
366
433
  }
367
434
  if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
368
- for (var i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
435
+ for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
369
436
  return -1;
370
437
  };
371
438
 
372
-
373
439
  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
374
440
  _.lastIndexOf = function(array, item) {
375
441
  if (array == null) return -1;
376
442
  if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
377
443
  var i = array.length;
378
- while (i--) if (array[i] === item) return i;
444
+ while (i--) if (i in array && array[i] === item) return i;
379
445
  return -1;
380
446
  };
381
447
 
@@ -383,30 +449,46 @@
383
449
  // the native Python `range()` function. See
384
450
  // [the Python documentation](http://docs.python.org/library/functions.html#range).
385
451
  _.range = function(start, stop, step) {
386
- var args = slice.call(arguments),
387
- solo = args.length <= 1,
388
- start = solo ? 0 : args[0],
389
- stop = solo ? args[0] : args[1],
390
- step = args[2] || 1,
391
- len = Math.max(Math.ceil((stop - start) / step), 0),
392
- idx = 0,
393
- range = new Array(len);
394
- while (idx < len) {
452
+ if (arguments.length <= 1) {
453
+ stop = start || 0;
454
+ start = 0;
455
+ }
456
+ step = arguments[2] || 1;
457
+
458
+ var len = Math.max(Math.ceil((stop - start) / step), 0);
459
+ var idx = 0;
460
+ var range = new Array(len);
461
+
462
+ while(idx < len) {
395
463
  range[idx++] = start;
396
464
  start += step;
397
465
  }
466
+
398
467
  return range;
399
468
  };
400
469
 
401
470
  // Function (ahem) Functions
402
471
  // ------------------
403
472
 
473
+ // Reusable constructor function for prototype setting.
474
+ var ctor = function(){};
475
+
404
476
  // Create a function bound to a given object (assigning `this`, and arguments,
405
477
  // optionally). Binding with arguments is also known as `curry`.
406
- _.bind = function(func, obj) {
407
- var args = slice.call(arguments, 2);
408
- return function() {
409
- return func.apply(obj || {}, args.concat(slice.call(arguments)));
478
+ // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
479
+ // We check for `func.bind` first, to fail fast when `func` is undefined.
480
+ _.bind = function bind(func, context) {
481
+ var bound, args;
482
+ if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
483
+ if (!_.isFunction(func)) throw new TypeError;
484
+ args = slice.call(arguments, 2);
485
+ return bound = function() {
486
+ if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
487
+ ctor.prototype = func.prototype;
488
+ var self = new ctor;
489
+ var result = func.apply(self, args.concat(slice.call(arguments)));
490
+ if (Object(result) === result) return result;
491
+ return self;
410
492
  };
411
493
  };
412
494
 
@@ -422,10 +504,10 @@
422
504
  // Memoize an expensive function by storing its results.
423
505
  _.memoize = function(func, hasher) {
424
506
  var memo = {};
425
- hasher = hasher || _.identity;
507
+ hasher || (hasher = _.identity);
426
508
  return function() {
427
509
  var key = hasher.apply(this, arguments);
428
- return key in memo ? memo[key] : (memo[key] = func.apply(this, arguments));
510
+ return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
429
511
  };
430
512
  };
431
513
 
@@ -433,7 +515,7 @@
433
515
  // it with the arguments supplied.
434
516
  _.delay = function(func, wait) {
435
517
  var args = slice.call(arguments, 2);
436
- return setTimeout(function(){ return func.apply(func, args); }, wait);
518
+ return setTimeout(function(){ return func.apply(null, args); }, wait);
437
519
  };
438
520
 
439
521
  // Defers a function, scheduling it to run after the current call stack has
@@ -442,31 +524,57 @@
442
524
  return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
443
525
  };
444
526
 
445
- // Internal function used to implement `_.throttle` and `_.debounce`.
446
- var limit = function(func, wait, debounce) {
447
- var timeout;
527
+ // Returns a function, that, when invoked, will only be triggered at most once
528
+ // during a given window of time.
529
+ _.throttle = function(func, wait) {
530
+ var context, args, timeout, throttling, more, result;
531
+ var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
448
532
  return function() {
449
- var context = this, args = arguments;
450
- var throttler = function() {
533
+ context = this; args = arguments;
534
+ var later = function() {
451
535
  timeout = null;
452
- func.apply(context, args);
536
+ if (more) func.apply(context, args);
537
+ whenDone();
453
538
  };
454
- if (debounce) clearTimeout(timeout);
455
- if (debounce || !timeout) timeout = setTimeout(throttler, wait);
539
+ if (!timeout) timeout = setTimeout(later, wait);
540
+ if (throttling) {
541
+ more = true;
542
+ } else {
543
+ result = func.apply(context, args);
544
+ }
545
+ whenDone();
546
+ throttling = true;
547
+ return result;
456
548
  };
457
549
  };
458
550
 
459
- // Returns a function, that, when invoked, will only be triggered at most once
460
- // during a given window of time.
461
- _.throttle = function(func, wait) {
462
- return limit(func, wait, false);
463
- };
464
-
465
551
  // Returns a function, that, as long as it continues to be invoked, will not
466
552
  // be triggered. The function will be called after it stops being called for
467
- // N milliseconds.
468
- _.debounce = function(func, wait) {
469
- return limit(func, wait, true);
553
+ // N milliseconds. If `immediate` is passed, trigger the function on the
554
+ // leading edge, instead of the trailing.
555
+ _.debounce = function(func, wait, immediate) {
556
+ var timeout;
557
+ return function() {
558
+ var context = this, args = arguments;
559
+ var later = function() {
560
+ timeout = null;
561
+ if (!immediate) func.apply(context, args);
562
+ };
563
+ if (immediate && !timeout) func.apply(context, args);
564
+ clearTimeout(timeout);
565
+ timeout = setTimeout(later, wait);
566
+ };
567
+ };
568
+
569
+ // Returns a function that will be executed at most one time, no matter how
570
+ // often you call it. Useful for lazy initialization.
571
+ _.once = function(func) {
572
+ var ran = false, memo;
573
+ return function() {
574
+ if (ran) return memo;
575
+ ran = true;
576
+ return memo = func.apply(this, arguments);
577
+ };
470
578
  };
471
579
 
472
580
  // Returns the first function passed as an argument to the second,
@@ -474,7 +582,7 @@
474
582
  // conditionally execute the original function.
475
583
  _.wrap = function(func, wrapper) {
476
584
  return function() {
477
- var args = [func].concat(slice.call(arguments));
585
+ var args = [func].concat(slice.call(arguments, 0));
478
586
  return wrapper.apply(this, args);
479
587
  };
480
588
  };
@@ -482,25 +590,33 @@
482
590
  // Returns a function that is the composition of a list of functions, each
483
591
  // consuming the return value of the function that follows.
484
592
  _.compose = function() {
485
- var funcs = slice.call(arguments);
593
+ var funcs = arguments;
486
594
  return function() {
487
- var args = slice.call(arguments);
488
- for (var i=funcs.length-1; i >= 0; i--) {
595
+ var args = arguments;
596
+ for (var i = funcs.length - 1; i >= 0; i--) {
489
597
  args = [funcs[i].apply(this, args)];
490
598
  }
491
599
  return args[0];
492
600
  };
493
601
  };
494
602
 
603
+ // Returns a function that will only be executed after being called N times.
604
+ _.after = function(times, func) {
605
+ if (times <= 0) return func();
606
+ return function() {
607
+ if (--times < 1) { return func.apply(this, arguments); }
608
+ };
609
+ };
610
+
495
611
  // Object Functions
496
612
  // ----------------
497
613
 
498
614
  // Retrieve the names of an object's properties.
499
615
  // Delegates to **ECMAScript 5**'s native `Object.keys`
500
616
  _.keys = nativeKeys || function(obj) {
501
- if (_.isArray(obj)) return _.range(0, obj.length);
617
+ if (obj !== Object(obj)) throw new TypeError('Invalid object');
502
618
  var keys = [];
503
- for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key;
619
+ for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
504
620
  return keys;
505
621
  };
506
622
 
@@ -512,19 +628,45 @@
512
628
  // Return a sorted list of the function names available on the object.
513
629
  // Aliased as `methods`
514
630
  _.functions = _.methods = function(obj) {
515
- return _.filter(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort();
631
+ var names = [];
632
+ for (var key in obj) {
633
+ if (_.isFunction(obj[key])) names.push(key);
634
+ }
635
+ return names.sort();
516
636
  };
517
637
 
518
638
  // Extend a given object with all the properties in passed-in object(s).
519
639
  _.extend = function(obj) {
520
640
  each(slice.call(arguments, 1), function(source) {
521
- for (var prop in source) obj[prop] = source[prop];
641
+ for (var prop in source) {
642
+ obj[prop] = source[prop];
643
+ }
644
+ });
645
+ return obj;
646
+ };
647
+
648
+ // Return a copy of the object only containing the whitelisted properties.
649
+ _.pick = function(obj) {
650
+ var result = {};
651
+ each(_.flatten(slice.call(arguments, 1)), function(key) {
652
+ if (key in obj) result[key] = obj[key];
653
+ });
654
+ return result;
655
+ };
656
+
657
+ // Fill in a given object with default properties.
658
+ _.defaults = function(obj) {
659
+ each(slice.call(arguments, 1), function(source) {
660
+ for (var prop in source) {
661
+ if (obj[prop] == null) obj[prop] = source[prop];
662
+ }
522
663
  });
523
664
  return obj;
524
665
  };
525
666
 
526
667
  // Create a (shallow-cloned) duplicate of an object.
527
668
  _.clone = function(obj) {
669
+ if (!_.isObject(obj)) return obj;
528
670
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
529
671
  };
530
672
 
@@ -536,49 +678,105 @@
536
678
  return obj;
537
679
  };
538
680
 
539
- // Perform a deep comparison to check if two objects are equal.
540
- _.isEqual = function(a, b) {
541
- // Check object identity.
542
- if (a === b) return true;
543
- // Different types?
544
- var atype = typeof(a), btype = typeof(b);
545
- if (atype != btype) return false;
546
- // Basic equality test (watch out for coercions).
547
- if (a == b) return true;
548
- // One is falsy and the other truthy.
549
- if ((!a && b) || (a && !b)) return false;
681
+ // Internal recursive comparison function.
682
+ function eq(a, b, stack) {
683
+ // Identical objects are equal. `0 === -0`, but they aren't identical.
684
+ // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
685
+ if (a === b) return a !== 0 || 1 / a == 1 / b;
686
+ // A strict comparison is necessary because `null == undefined`.
687
+ if (a == null || b == null) return a === b;
550
688
  // Unwrap any wrapped objects.
551
689
  if (a._chain) a = a._wrapped;
552
690
  if (b._chain) b = b._wrapped;
553
- // One of them implements an isEqual()?
554
- if (a.isEqual) return a.isEqual(b);
555
- // Check dates' integer values.
556
- if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
557
- // Both are NaN?
558
- if (_.isNaN(a) && _.isNaN(b)) return false;
559
- // Compare regular expressions.
560
- if (_.isRegExp(a) && _.isRegExp(b))
561
- return a.source === b.source &&
562
- a.global === b.global &&
563
- a.ignoreCase === b.ignoreCase &&
564
- a.multiline === b.multiline;
565
- // If a is not an object by this point, we can't handle it.
566
- if (atype !== 'object') return false;
567
- // Check for different array lengths before comparing contents.
568
- if (a.length && (a.length !== b.length)) return false;
569
- // Nothing else worked, deep compare the contents.
570
- var aKeys = _.keys(a), bKeys = _.keys(b);
571
- // Different object sizes?
572
- if (aKeys.length != bKeys.length) return false;
573
- // Recursive comparison of contents.
574
- for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false;
575
- return true;
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);
694
+ // Compare `[[Class]]` names.
695
+ var className = toString.call(a);
696
+ if (className != toString.call(b)) return false;
697
+ switch (className) {
698
+ // Strings, numbers, dates, and booleans are compared by value.
699
+ case '[object String]':
700
+ // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
701
+ // equivalent to `new String("5")`.
702
+ return a == String(b);
703
+ case '[object Number]':
704
+ // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
705
+ // other numeric values.
706
+ return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
707
+ case '[object Date]':
708
+ case '[object Boolean]':
709
+ // Coerce dates and booleans to numeric primitive values. Dates are compared by their
710
+ // millisecond representations. Note that invalid dates with millisecond representations
711
+ // of `NaN` are not equivalent.
712
+ return +a == +b;
713
+ // RegExps are compared by their source patterns and flags.
714
+ case '[object RegExp]':
715
+ return a.source == b.source &&
716
+ a.global == b.global &&
717
+ a.multiline == b.multiline &&
718
+ a.ignoreCase == b.ignoreCase;
719
+ }
720
+ if (typeof a != 'object' || typeof b != 'object') return false;
721
+ // Assume equality for cyclic structures. The algorithm for detecting cyclic
722
+ // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
723
+ var length = stack.length;
724
+ while (length--) {
725
+ // Linear search. Performance is inversely proportional to the number of
726
+ // unique nested structures.
727
+ if (stack[length] == a) return true;
728
+ }
729
+ // Add the first object to the stack of traversed objects.
730
+ stack.push(a);
731
+ var size = 0, result = true;
732
+ // Recursively compare objects and arrays.
733
+ if (className == '[object Array]') {
734
+ // Compare array lengths to determine if a deep comparison is necessary.
735
+ size = a.length;
736
+ result = size == b.length;
737
+ if (result) {
738
+ // Deep compare the contents, ignoring non-numeric properties.
739
+ 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;
742
+ }
743
+ }
744
+ } else {
745
+ // Objects with different constructors are not equivalent.
746
+ if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
747
+ // Deep compare objects.
748
+ for (var key in a) {
749
+ if (_.has(a, key)) {
750
+ // Count the expected number of properties.
751
+ size++;
752
+ // Deep compare each member.
753
+ if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
754
+ }
755
+ }
756
+ // Ensure that both objects contain the same number of properties.
757
+ if (result) {
758
+ for (key in b) {
759
+ if (_.has(b, key) && !(size--)) break;
760
+ }
761
+ result = !size;
762
+ }
763
+ }
764
+ // Remove the first object from the stack of traversed objects.
765
+ stack.pop();
766
+ return result;
767
+ }
768
+
769
+ // Perform a deep comparison to check if two objects are equal.
770
+ _.isEqual = function(a, b) {
771
+ return eq(a, b, []);
576
772
  };
577
773
 
578
- // Is a given array or object empty?
774
+ // Is a given array, string, or object empty?
775
+ // An "empty" object has no enumerable own-properties.
579
776
  _.isEmpty = function(obj) {
777
+ if (obj == null) return true;
580
778
  if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
581
- for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
779
+ for (var key in obj) if (_.has(obj, key)) return false;
582
780
  return true;
583
781
  };
584
782
 
@@ -590,48 +788,63 @@
590
788
  // Is a given value an array?
591
789
  // Delegates to ECMA5's native Array.isArray
592
790
  _.isArray = nativeIsArray || function(obj) {
593
- return toString.call(obj) === '[object Array]';
791
+ return toString.call(obj) == '[object Array]';
792
+ };
793
+
794
+ // Is a given variable an object?
795
+ _.isObject = function(obj) {
796
+ return obj === Object(obj);
594
797
  };
595
798
 
596
799
  // Is a given variable an arguments object?
597
800
  _.isArguments = function(obj) {
598
- return !!(obj && hasOwnProperty.call(obj, 'callee'));
801
+ return toString.call(obj) == '[object Arguments]';
599
802
  };
803
+ if (!_.isArguments(arguments)) {
804
+ _.isArguments = function(obj) {
805
+ return !!(obj && _.has(obj, 'callee'));
806
+ };
807
+ }
600
808
 
601
809
  // Is a given value a function?
602
810
  _.isFunction = function(obj) {
603
- return !!(obj && obj.constructor && obj.call && obj.apply);
811
+ return toString.call(obj) == '[object Function]';
604
812
  };
605
813
 
606
814
  // Is a given value a string?
607
815
  _.isString = function(obj) {
608
- return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
816
+ return toString.call(obj) == '[object String]';
609
817
  };
610
818
 
611
819
  // Is a given value a number?
612
820
  _.isNumber = function(obj) {
613
- return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed));
821
+ return toString.call(obj) == '[object Number]';
614
822
  };
615
823
 
616
- // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript
617
- // that does not equal itself.
824
+ // Is a given object a finite number?
825
+ _.isFinite = function(obj) {
826
+ return _.isNumber(obj) && isFinite(obj);
827
+ };
828
+
829
+ // Is the given value `NaN`?
618
830
  _.isNaN = function(obj) {
831
+ // `NaN` is the only value for which `===` is not reflexive.
619
832
  return obj !== obj;
620
833
  };
621
834
 
622
835
  // Is a given value a boolean?
623
836
  _.isBoolean = function(obj) {
624
- return obj === true || obj === false;
837
+ return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
625
838
  };
626
839
 
627
840
  // Is a given value a date?
628
841
  _.isDate = function(obj) {
629
- return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear);
842
+ return toString.call(obj) == '[object Date]';
630
843
  };
631
844
 
632
845
  // Is the given value a regular expression?
633
846
  _.isRegExp = function(obj) {
634
- return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false));
847
+ return toString.call(obj) == '[object RegExp]';
635
848
  };
636
849
 
637
850
  // Is a given value equal to null?
@@ -644,6 +857,11 @@
644
857
  return obj === void 0;
645
858
  };
646
859
 
860
+ // Has own property?
861
+ _.has = function(obj, key) {
862
+ return hasOwnProperty.call(obj, key);
863
+ };
864
+
647
865
  // Utility Functions
648
866
  // -----------------
649
867
 
@@ -664,6 +882,19 @@
664
882
  for (var i = 0; i < n; i++) iterator.call(context, i);
665
883
  };
666
884
 
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;');
888
+ };
889
+
890
+ // If the value of the named property is a function then invoke it;
891
+ // otherwise, return it.
892
+ _.result = function(object, property) {
893
+ if (object == null) return null;
894
+ var value = object[property];
895
+ return _.isFunction(value) ? value.call(object) : value;
896
+ };
897
+
667
898
  // Add your own custom functions to the Underscore object, ensuring that
668
899
  // they're correctly added to the OOP wrapper as well.
669
900
  _.mixin = function(obj) {
@@ -684,31 +915,86 @@
684
915
  // following template settings to use alternative delimiters.
685
916
  _.templateSettings = {
686
917
  evaluate : /<%([\s\S]+?)%>/g,
687
- interpolate : /<%=([\s\S]+?)%>/g
918
+ interpolate : /<%=([\s\S]+?)%>/g,
919
+ escape : /<%-([\s\S]+?)%>/g
920
+ };
921
+
922
+ // When customizing `templateSettings`, if you don't want to define an
923
+ // interpolation, evaluation or escaping regex, we need one that is
924
+ // guaranteed not to match.
925
+ var noMatch = /.^/;
926
+
927
+ // Certain characters need to be escaped so that they can be put into a
928
+ // string literal.
929
+ var escapes = {
930
+ '\\': '\\',
931
+ "'": "'",
932
+ 'r': '\r',
933
+ 'n': '\n',
934
+ 't': '\t',
935
+ 'u2028': '\u2028',
936
+ 'u2029': '\u2029'
937
+ };
938
+
939
+ for (var p in escapes) escapes[escapes[p]] = p;
940
+ 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
+ });
688
949
  };
689
950
 
690
951
  // JavaScript micro-templating, similar to John Resig's implementation.
691
952
  // Underscore templating handles arbitrary delimiters, preserves whitespace,
692
953
  // and correctly escapes quotes within interpolated code.
693
- _.template = function(str, data) {
694
- var c = _.templateSettings;
695
- var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
696
- 'with(obj||{}){__p.push(\'' +
697
- str.replace(/\\/g, '\\\\')
698
- .replace(/'/g, "\\'")
699
- .replace(c.interpolate, function(match, code) {
700
- return "'," + code.replace(/\\'/g, "'") + ",'";
701
- })
702
- .replace(c.evaluate || null, function(match, code) {
703
- return "');" + code.replace(/\\'/g, "'")
704
- .replace(/[\r\n\t]/g, ' ') + "__p.push('";
705
- })
706
- .replace(/\r/g, '\\r')
707
- .replace(/\n/g, '\\n')
708
- .replace(/\t/g, '\\t')
709
- + "');}return __p.join('');";
710
- var func = new Function('obj', tmpl);
711
- return data ? func(data) : func;
954
+ _.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";
973
+
974
+ // If a variable is not specified, place data values in local scope.
975
+ if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
976
+
977
+ source = "var __p='';" +
978
+ "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" +
979
+ source + "return __p;\n";
980
+
981
+ var render = new Function(settings.variable || 'obj', '_', source);
982
+ if (data) return render(data, _);
983
+ var template = function(data) {
984
+ return render.call(this, data, _);
985
+ };
986
+
987
+ // Provide the compiled function source as a convenience for build time
988
+ // precompilation.
989
+ template.source = 'function(' + (settings.variable || 'obj') + '){\n' +
990
+ source + '}';
991
+
992
+ return template;
993
+ };
994
+
995
+ // Add a "chain" function, which will delegate to the wrapper.
996
+ _.chain = function(obj) {
997
+ return _(obj).chain();
712
998
  };
713
999
 
714
1000
  // The OOP Wrapper
@@ -743,8 +1029,11 @@
743
1029
  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
744
1030
  var method = ArrayProto[name];
745
1031
  wrapper.prototype[name] = function() {
746
- method.apply(this._wrapped, arguments);
747
- return result(this._wrapped, this._chain);
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);
748
1037
  };
749
1038
  });
750
1039
 
@@ -767,4 +1056,4 @@
767
1056
  return this._wrapped;
768
1057
  };
769
1058
 
770
- })();
1059
+ }).call(this);