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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
}
|
57
|
-
}
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
|
@@ -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',
|
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.
|
1
|
+
// Underscore.js 1.4.2
|
2
|
+
// http://underscorejs.org
|
2
3
|
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
3
|
-
// Underscore
|
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
|
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) {
|
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.
|
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 (
|
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
|
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
|
-
|
134
|
-
|
135
|
-
|
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
|
201
|
-
// Aliased as `
|
202
|
-
_.
|
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
|
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]
|
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]
|
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
|
252
|
-
|
253
|
-
|
254
|
-
|
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,
|
262
|
-
var iterator =
|
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
|
270
|
-
|
271
|
-
if (
|
272
|
-
|
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
|
-
//
|
277
|
-
|
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 =
|
324
|
+
var iterator = lookupIterator(value);
|
281
325
|
each(obj, function(value, index) {
|
282
|
-
var key = iterator(value, index);
|
283
|
-
(result
|
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
|
-
//
|
289
|
-
//
|
290
|
-
_.
|
291
|
-
|
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)
|
295
|
-
iterator(array[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)
|
303
|
-
if (
|
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
|
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.
|
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 **
|
344
|
-
// the rest
|
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,
|
347
|
-
return slice.call(array, (
|
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
|
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
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
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
|
-
|
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(
|
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.
|
395
|
-
_.intersection =
|
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 =
|
408
|
-
return _.filter(array, function(value){ return !_.
|
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++)
|
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
|
-
|
432
|
-
|
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 (
|
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
|
-
|
443
|
-
|
444
|
-
|
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)
|
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
|
-
|
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
|
-
|
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]
|
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) {
|
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
|
-
|
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
|
651
|
-
|
652
|
-
|
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
|
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
|
-
|
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
|
690
|
-
if (b
|
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 =
|
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 (
|
851
|
+
if (aStack[length] == a) return bStack[length] == b;
|
728
852
|
}
|
729
853
|
// Add the first object to the stack of traversed objects.
|
730
|
-
|
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
|
-
|
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
|
-
|
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],
|
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
|
-
|
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
|
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
|
-
//
|
800
|
-
|
801
|
-
|
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
|
-
//
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
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
|
-
|
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
|
-
//
|
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
|
998
|
+
_.times = function(n, iterator, context) {
|
882
999
|
for (var i = 0; i < n; i++) iterator.call(context, i);
|
883
1000
|
};
|
884
1001
|
|
885
|
-
//
|
886
|
-
_.
|
887
|
-
|
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
|
+
'&': '&',
|
1015
|
+
'<': '<',
|
1016
|
+
'>': '>',
|
1017
|
+
'"': '"',
|
1018
|
+
"'": ''',
|
1019
|
+
'/': '/'
|
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
|
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
|
-
|
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':
|
933
|
-
'n':
|
934
|
-
't':
|
935
|
-
'u2028': '
|
936
|
-
'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(
|
956
|
-
|
957
|
-
//
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
.
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
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
|
-
"
|
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
|
-
|
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
|
988
|
-
|
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
|
-
//
|
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
|
1013
|
-
return
|
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
|
-
|
1032
|
-
var
|
1033
|
-
method.apply(
|
1034
|
-
|
1035
|
-
|
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
|
-
|
1044
|
-
return result(method.apply(this._wrapped, arguments)
|
1180
|
+
_.prototype[name] = function() {
|
1181
|
+
return result.call(this, method.apply(this._wrapped, arguments));
|
1045
1182
|
};
|
1046
1183
|
});
|
1047
1184
|
|
1048
|
-
|
1049
|
-
wrapper.prototype.chain = function() {
|
1050
|
-
this._chain = true;
|
1051
|
-
return this;
|
1052
|
-
};
|
1185
|
+
_.extend(_.prototype, {
|
1053
1186
|
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
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.
|
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-
|
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.
|
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
|