sugar-rails 1.2.5 → 1.2.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4001 @@
1
+ // Google Closure Compiler will output a wrapping function here.
2
+ (function() {
3
+
4
+ // A few optimizations for Google Closure Compiler will save us a couple kb in the release script.
5
+ var object = Object, array = Array, regexp = RegExp, date = Date, string = String, number = Number, Undefined;
6
+
7
+ // defineProperty exists in IE8 but will error when trying to define a property on
8
+ // native objects. IE8 does not have defineProperies, however, so this check saves a try/catch block.
9
+ var definePropertySupport = object.defineProperty && object.defineProperties;
10
+
11
+ // Class extending methods
12
+
13
+ function extend(klass, instance, override, methods) {
14
+ var extendee = instance ? klass.prototype : klass, original;
15
+ initializeClass(klass, instance, methods);
16
+ iterateOverObject(methods, function(name, method) {
17
+ original = extendee[name];
18
+ if(typeof override === 'function') {
19
+ method = wrapNative(extendee[name], method, override);
20
+ }
21
+ if(override !== false || !extendee[name]) {
22
+ defineProperty(extendee, name, method);
23
+ }
24
+ // If the method is internal to Sugar, then store a reference so it can be restored later.
25
+ klass['SugarMethods'][name] = { instance: instance, method: method, original: original };
26
+ });
27
+ }
28
+
29
+ function initializeClass(klass) {
30
+ if(klass.SugarMethods) return;
31
+ defineProperty(klass, 'SugarMethods', {});
32
+ extend(klass, false, false, {
33
+ 'restore': function() {
34
+ var all = arguments.length === 0, methods = multiArgs(arguments);
35
+ iterateOverObject(klass['SugarMethods'], function(name, m) {
36
+ if(all || methods.has(name)) {
37
+ defineProperty(m.instance ? klass.prototype : klass, name, m.method);
38
+ }
39
+ });
40
+ },
41
+ 'extend': function(methods, override, instance) {
42
+ if(klass === object && arguments.length === 0) {
43
+ mapObjectPrototypeMethods();
44
+ } else {
45
+ extend(klass, instance !== false, override, methods);
46
+ }
47
+ }
48
+ });
49
+ }
50
+
51
+ function wrapNative(nativeFn, extendedFn, condition) {
52
+ return function() {
53
+ if(nativeFn && (condition === true || !condition.apply(this, arguments))) {
54
+ return nativeFn.apply(this, arguments);
55
+ } else {
56
+ return extendedFn.apply(this, arguments);
57
+ }
58
+ }
59
+ }
60
+
61
+ function defineProperty(target, name, method) {
62
+ if(definePropertySupport) {
63
+ object.defineProperty(target, name, { 'value': method, 'configurable': true, 'enumerable': false, 'writable': true });
64
+ } else {
65
+ target[name] = method;
66
+ }
67
+ }
68
+
69
+ // Object helpers
70
+
71
+ function hasOwnProperty(obj, key) {
72
+ return object.prototype.hasOwnProperty.call(obj, key);
73
+ }
74
+
75
+ function iterateOverObject(obj, fn) {
76
+ var key;
77
+ for(key in obj) {
78
+ if(!hasOwnProperty(obj, key)) continue;
79
+ fn.call(obj, key, obj[key]);
80
+ }
81
+ }
82
+
83
+ function multiMatch(el, match, scope, params) {
84
+ var result = true;
85
+ if(el === match) {
86
+ // Match strictly equal values up front.
87
+ return true;
88
+ } else if(object.isRegExp(match)) {
89
+ // Match against a regexp
90
+ return regexp(match).test(el);
91
+ } else if(object.isFunction(match)) {
92
+ // Match against a filtering function
93
+ return match.apply(scope, [el].concat(params));
94
+ } else if(object.isObject(match) && object.isObject(el)) {
95
+ // Match against a hash or array.
96
+ iterateOverObject(match, function(key, value) {
97
+ if(!multiMatch(el[key], match[key], scope, params)) {
98
+ result = false;
99
+ }
100
+ });
101
+ return !object.isEmpty(match) && result;
102
+ } else {
103
+ return object.equal(el, match);
104
+ }
105
+ }
106
+
107
+ function stringify(thing, stack) {
108
+ var value, klass, isObject, isArray, arr, i, key, type = typeof thing;
109
+
110
+ // Return quickly if string to save cycles
111
+ if(type === 'string') return thing;
112
+
113
+ klass = object.prototype.toString.call(thing)
114
+ isObject = klass === '[object Object]';
115
+ isArray = klass === '[object Array]';
116
+
117
+ if(thing != null && isObject || isArray) {
118
+ // This method for checking for cyclic structures was egregiously stolen from
119
+ // the ingenious method by @kitcambridge from the Underscore script:
120
+ // https://github.com/documentcloud/underscore/issues/240
121
+ if(!stack) stack = [];
122
+ // Allowing a step into the structure before triggering this
123
+ // script to save cycles on standard JSON structures and also to
124
+ // try as hard as possible to catch basic properties that may have
125
+ // been modified.
126
+ if(stack.length > 1) {
127
+ i = stack.length;
128
+ while (i--) {
129
+ if (stack[i] === thing) {
130
+ return 'CYC';
131
+ }
132
+ }
133
+ }
134
+ stack.push(thing);
135
+ value = string(thing.constructor);
136
+ arr = isArray ? thing : object.keys(thing).sort();
137
+ for(i = 0; i < arr.length; i++) {
138
+ key = isArray ? i : arr[i];
139
+ value += key + stringify(thing[key], stack);
140
+ }
141
+ stack.pop();
142
+ } else if(1 / thing === -Infinity) {
143
+ value = '-0';
144
+ } else {
145
+ value = string(thing);
146
+ }
147
+ return type + klass + value;
148
+ }
149
+
150
+
151
+ // Argument helpers
152
+
153
+ function transformArgument(el, map, context, mapArgs) {
154
+ if(isUndefined(map)) {
155
+ return el;
156
+ } else if(object.isFunction(map)) {
157
+ return map.apply(context, mapArgs || []);
158
+ } else if(object.isFunction(el[map])) {
159
+ return el[map].call(el);
160
+ } else {
161
+ return el[map];
162
+ }
163
+ }
164
+
165
+ function getArgs(args, index) {
166
+ return Array.prototype.slice.call(args, index);
167
+ }
168
+
169
+ function multiArgs(args, fn, flatten, index) {
170
+ args = getArgs(args);
171
+ if(flatten === true) args = arrayFlatten(args, 1);
172
+ arrayEach(args, fn || function(){}, index);
173
+ return args;
174
+ }
175
+
176
+
177
+ // Used for both arrays and strings
178
+
179
+ function entryAtIndex(arr, args, str) {
180
+ var result = [], length = arr.length, loop = args[args.length - 1] !== false, r;
181
+ multiArgs(args, function(index) {
182
+ if(object.isBoolean(index)) return false;
183
+ if(loop) {
184
+ index = index % length;
185
+ if(index < 0) index = length + index;
186
+ }
187
+ r = str ? arr.charAt(index) || '' : arr[index];
188
+ result.push(r);
189
+ });
190
+ return result.length < 2 ? result[0] : result;
191
+ }
192
+
193
+ /***
194
+ * Object module
195
+ *
196
+ * Much thanks to kangax for his informative aricle about how problems with instanceof and constructor
197
+ * http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
198
+ *
199
+ ***/
200
+
201
+ function isClass(obj, str) {
202
+ return object.prototype.toString.call(obj) === '[object '+str+']';
203
+ }
204
+
205
+ function isUndefined(o) {
206
+ return o === Undefined;
207
+ }
208
+
209
+ function setParamsObject(obj, param, value, deep) {
210
+ var reg = /^(.+?)(\[.*\])$/, isArray, match, allKeys, key;
211
+ if(deep !== false && (match = param.match(reg))) {
212
+ key = match[1];
213
+ allKeys = match[2].replace(/^\[|\]$/g, '').split('][');
214
+ arrayEach(allKeys, function(k) {
215
+ isArray = !k || k.match(/^\d+$/);
216
+ if(!key && object.isArray(obj)) key = obj.length;
217
+ if(!obj[key]) {
218
+ obj[key] = isArray ? [] : {};
219
+ }
220
+ obj = obj[key];
221
+ key = k;
222
+ });
223
+ if(!key && isArray) key = obj.length.toString();
224
+ setParamsObject(obj, key, value);
225
+ } else if(value.match(/^[\d.]+$/)) {
226
+ obj[param] = parseFloat(value);
227
+ } else if(value === 'true') {
228
+ obj[param] = true;
229
+ } else if(value === 'false') {
230
+ obj[param] = false;
231
+ } else {
232
+ obj[param] = value;
233
+ }
234
+ }
235
+
236
+ function Hash(obj) {
237
+ var self = this;
238
+ iterateOverObject(obj, function(key, value) {
239
+ self[key] = value;
240
+ });
241
+ }
242
+
243
+ /***
244
+ * @method is[Type](<obj>)
245
+ * @returns Boolean
246
+ * @short Returns true if <obj> is an object of that type.
247
+ * @extra %isObject% will return false on anything that is not an object literal, including instances of inherited classes. Note also that %isNaN% will ONLY return true if the object IS %NaN%. It does not mean the same as browser native %isNaN%, which returns true for anything that is "not a number". Type methods are available as instance methods on extended objects.
248
+ * @example
249
+ *
250
+ * Object.isArray([1,2,3]) -> true
251
+ * Object.isDate(3) -> false
252
+ * Object.isRegExp(/wasabi/) -> true
253
+ * Object.isObject({ broken:'wear' }) -> true
254
+ *
255
+ ***
256
+ * @method isArray()
257
+ * @set isType
258
+ ***
259
+ * @method isBoolean()
260
+ * @set isType
261
+ ***
262
+ * @method isDate()
263
+ * @set isType
264
+ ***
265
+ * @method isFunction()
266
+ * @set isType
267
+ ***
268
+ * @method isNumber()
269
+ * @set isType
270
+ ***
271
+ * @method isString()
272
+ * @set isType
273
+ ***
274
+ * @method isRegExp()
275
+ * @set isType
276
+ ***/
277
+
278
+
279
+ var ObjectTypeMethods = ['isObject','isNaN'];
280
+ var ObjectHashMethods = ['keys','values','each','merge','isEmpty','clone','equal','watch','tap','has']
281
+
282
+ function buildTypeMethods() {
283
+ var methods = {}, name;
284
+ arrayEach(['Array','Boolean','Date','Function','Number','String','RegExp'], function(type) {
285
+ name = 'is' + type;
286
+ ObjectTypeMethods.push(name);
287
+ methods[name] = function(obj) {
288
+ return isClass(obj, type);
289
+ }
290
+ });
291
+ extend(Object, false, false, methods);
292
+ }
293
+
294
+ function buildInstanceMethods(set, target) {
295
+ var methods = {};
296
+ arrayEach(set, function(name) {
297
+ methods[name + (name === 'equal' ? 's' : '')] = function() {
298
+ return Object[name].apply(null, [this].concat(getArgs(arguments)));
299
+ }
300
+ });
301
+ extend(target, true, false, methods);
302
+ }
303
+
304
+ function buildObject() {
305
+ buildTypeMethods();
306
+ buildInstanceMethods(ObjectHashMethods, Hash);
307
+ }
308
+
309
+ function mapObjectPrototypeMethods() {
310
+ buildInstanceMethods(ObjectTypeMethods.concat(ObjectHashMethods), Object);
311
+ }
312
+
313
+ extend(object, false, true, {
314
+ /***
315
+ * @method watch(<obj>, <prop>, <fn>)
316
+ * @returns Nothing
317
+ * @short Watches a property of <obj> and runs <fn> when it changes.
318
+ * @extra <fn> is passed three arguments: the property <prop>, the old value, and the new value. The return value of [fn] will be set as the new value. This method is useful for things such as validating or cleaning the value when it is set. Warning: this method WILL NOT work in browsers that don't support %Object.defineProperty%. This notably includes IE 8 and below, and Opera. This is the only method in Sugar that is not fully compatible with all browsers. %watch% is available as an instance method on extended objects.
319
+ * @example
320
+ *
321
+ * Object.watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
322
+ * // Will be run when the property 'foo' is set on the object.
323
+ * });
324
+ * Object.extended().watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
325
+ * // Will be run when the property 'foo' is set on the object.
326
+ * });
327
+ *
328
+ ***/
329
+ 'watch': function(obj, prop, fn) {
330
+ if(!definePropertySupport) return;
331
+ var value = obj[prop];
332
+ object.defineProperty(obj, prop, {
333
+ 'get': function() {
334
+ return value;
335
+ },
336
+ 'set': function(to) {
337
+ value = fn.call(obj, prop, value, to);
338
+ },
339
+ 'enumerable': true,
340
+ 'configurable': true
341
+ });
342
+ }
343
+ });
344
+
345
+ extend(object, false, false, {
346
+
347
+ /***
348
+ * @method Object.extended(<obj> = {})
349
+ * @returns Extended object
350
+ * @short Creates a new object, equivalent to %new Object()% or %{}%, but with extended methods.
351
+ * @extra See extended objects for more.
352
+ * @example
353
+ *
354
+ * Object.extended()
355
+ * Object.extended({ happy:true, pappy:false }).keys() -> ['happy','pappy']
356
+ * Object.extended({ happy:true, pappy:false }).values() -> [true, false]
357
+ *
358
+ ***/
359
+ 'extended': function(obj) {
360
+ return new Hash(obj);
361
+ },
362
+
363
+ /***
364
+ * @method isObject()
365
+ * @set isType
366
+ ***/
367
+ 'isObject': function(obj) {
368
+ if(obj == null) {
369
+ return false;
370
+ } else {
371
+ // === on the constructor is not safe across iframes
372
+ return isClass(obj, 'Object') && string(obj.constructor) === string(object) || obj.constructor === Hash;
373
+ }
374
+ },
375
+
376
+ /***
377
+ * @method isNaN()
378
+ * @set isType
379
+ ***/
380
+ 'isNaN': function(obj) {
381
+ // This is only true of NaN
382
+ return object.isNumber(obj) && obj.valueOf() !== obj.valueOf();
383
+ },
384
+
385
+ /***
386
+ * @method each(<obj>, [fn])
387
+ * @returns Object
388
+ * @short Iterates over each property in <obj> calling [fn] on each iteration.
389
+ * @extra %each% is available as an instance method on extended objects.
390
+ * @example
391
+ *
392
+ * Object.each({ broken:'wear' }, function(key, value) {
393
+ * // Iterates over each key/value pair.
394
+ * });
395
+ * Object.extended({ broken:'wear' }).each(function(key, value) {
396
+ * // Iterates over each key/value pair.
397
+ * });
398
+ *
399
+ ***/
400
+ 'each': function(obj, fn) {
401
+ if(fn) {
402
+ iterateOverObject(obj, function(k,v) {
403
+ fn.call(obj, k, v, obj);
404
+ });
405
+ }
406
+ return obj;
407
+ },
408
+
409
+ /***
410
+ * @method merge(<target>, <source>, [deep] = false, [resolve] = true)
411
+ * @returns Merged object
412
+ * @short Merges all the properties of <source> into <target>.
413
+ * @extra Merges are shallow unless [deep] is %true%. Properties of <source> will win in the case of conflicts, unless [resolve] is %false%. [resolve] can also be a function that resolves the conflict. In this case it will be passed 3 arguments, %key%, %targetVal%, and %sourceVal%, with the context set to <source>. This will allow you to solve conflict any way you want, ie. adding two numbers together, etc. %merge% is available as an instance method on extended objects.
414
+ * @example
415
+ *
416
+ * Object.merge({a:1},{b:2}) -> { a:1, b:2 }
417
+ * Object.merge({a:1},{a:2}, false, false) -> { a:1 }
418
+ + Object.merge({a:1},{a:2}, false, function(key, a, b) {
419
+ * return a + b;
420
+ * }); -> { a:3 }
421
+ * Object.extended({a:1}).merge({b:2}) -> { a:1, b:2 }
422
+ *
423
+ ***/
424
+ 'merge': function(target, source, deep, resolve) {
425
+ var key, val;
426
+ // Strings cannot be reliably merged thanks to
427
+ // their properties not being enumerable in < IE8.
428
+ if(target && typeof source != 'string') {
429
+ for(key in source) {
430
+ if(!hasOwnProperty(source, key) || !target) continue;
431
+ val = source[key];
432
+ // Conflict!
433
+ if(target[key] !== Undefined) {
434
+ // Do not merge.
435
+ if(resolve === false) {
436
+ continue;
437
+ }
438
+ // Use the result of the callback as the result.
439
+ if(object.isFunction(resolve)) {
440
+ val = resolve.call(source, key, target[key], source[key])
441
+ }
442
+ }
443
+ // Deep merging.
444
+ if(deep === true && val && typeof val === 'object') {
445
+ if(object.isDate(val)) {
446
+ val = new Date(val.getTime());
447
+ } else if(object.isRegExp(val)) {
448
+ val = new RegExp(val.source, val.getFlags());
449
+ } else {
450
+ if(!target[key]) target[key] = array.isArray(val) ? [] : {};
451
+ Object.merge(target[key], source[key], deep, resolve);
452
+ continue;
453
+ }
454
+ }
455
+ target[key] = val;
456
+ }
457
+ }
458
+ return target;
459
+ },
460
+
461
+ /***
462
+ * @method isEmpty(<obj>)
463
+ * @returns Boolean
464
+ * @short Returns true if <obj> is empty.
465
+ * @extra %isEmpty% is available as an instance method on extended objects.
466
+ * @example
467
+ *
468
+ * Object.isEmpty({}) -> true
469
+ * Object.isEmpty({foo:'bar'}) -> false
470
+ * Object.extended({foo:'bar'}).isEmpty() -> false
471
+ *
472
+ ***/
473
+ 'isEmpty': function(obj) {
474
+ if(obj == null || typeof obj != 'object') return !(obj && obj.length > 0);
475
+ return object.keys(obj).length == 0;
476
+ },
477
+
478
+ /***
479
+ * @method equal(<a>, <b>)
480
+ * @returns Boolean
481
+ * @short Returns true if <a> and <b> are equal.
482
+ * @extra %equal% in Sugar is "egal", meaning the values are equal if they are "not observably distinguishable". Note that on extended objects the name is %equals% for readability.
483
+ * @example
484
+ *
485
+ * Object.equal({a:2}, {a:2}) -> true
486
+ * Object.equal({a:2}, {a:3}) -> false
487
+ * Object.extended({a:2}).equals({a:3}) -> false
488
+ *
489
+ ***/
490
+ 'equal': function(a, b) {
491
+ return stringify(a) === stringify(b);
492
+ },
493
+
494
+ /***
495
+ * @method values(<obj>, [fn])
496
+ * @returns Array
497
+ * @short Returns an array containing the values in <obj>. Optionally calls [fn] for each value.
498
+ * @extra Returned values are in no particular order. %values% is available as an instance method on extended objects.
499
+ * @example
500
+ *
501
+ * Object.values({ broken: 'wear' }) -> ['wear']
502
+ * Object.values({ broken: 'wear' }, function(value) {
503
+ * // Called once for each value.
504
+ * });
505
+ * Object.extended({ broken: 'wear' }).values() -> ['wear']
506
+ *
507
+ ***/
508
+ 'values': function(obj, fn) {
509
+ var values = [];
510
+ iterateOverObject(obj, function(k,v) {
511
+ values.push(v);
512
+ if(fn) fn.call(obj,v);
513
+ });
514
+ return values;
515
+ },
516
+
517
+ /***
518
+ * @method clone(<obj> = {}, [deep] = false)
519
+ * @returns Cloned object
520
+ * @short Creates a clone (copy) of <obj>.
521
+ * @extra Default is a shallow clone, unless [deep] is true. %clone% is available as an instance method on extended objects.
522
+ * @example
523
+ *
524
+ * Object.clone({foo:'bar'}) -> { foo: 'bar' }
525
+ * Object.clone() -> {}
526
+ * Object.extended({foo:'bar'}).clone() -> { foo: 'bar' }
527
+ *
528
+ ***/
529
+ 'clone': function(obj, deep) {
530
+ if(obj == null || typeof obj !== 'object') return obj;
531
+ if(array.isArray(obj)) return obj.clone();
532
+ var target = obj.constructor === Hash ? new Hash() : {};
533
+ return object.merge(target, obj, deep);
534
+ },
535
+
536
+ /***
537
+ * @method Object.fromQueryString(<str>, [deep] = true)
538
+ * @returns Object
539
+ * @short Converts the query string of a URL into an object.
540
+ * @extra If [deep] is %false%, conversion will only accept shallow params (ie. no object or arrays with %[]% syntax) as these are not universally supported.
541
+ * @example
542
+ *
543
+ * Object.fromQueryString('foo=bar&broken=wear') -> { foo: 'bar', broken: 'wear' }
544
+ * Object.fromQueryString('foo[]=1&foo[]=2') -> { foo: [1,2] }
545
+ *
546
+ ***/
547
+ 'fromQueryString': function(str, deep) {
548
+ var result = object.extended(), split;
549
+ str = str && str.toString ? str.toString() : '';
550
+ str.replace(/^.*?\?/, '').unescapeURL().split('&').each(function(p) {
551
+ var split = p.split('=');
552
+ if(split.length !== 2) return;
553
+ setParamsObject(result, split[0], split[1], deep);
554
+ });
555
+ return result;
556
+ },
557
+
558
+ /***
559
+ * @method tap(<obj>, <fn>)
560
+ * @returns Object
561
+ * @short Runs <fn> and returns <obj>.
562
+ * @extra A string can also be used as a shortcut to a method. This method is used to run an intermediary function in the middle of method chaining. As a standalone method on the Object class it doesn't have too much use. The power of %tap% comes when using extended objects or modifying the Object prototype with Object.extend().
563
+ * @example
564
+ *
565
+ * Object.extend();
566
+ * [2,4,6].map(Math.exp).tap(function(){ arr.pop(); }).map(Math.round); -> [7,55]
567
+ * [2,4,6].map(Math.exp).tap('pop').map(Math.round); -> [7,55]
568
+ *
569
+ ***/
570
+ 'tap': function(obj, fn) {
571
+ transformArgument(obj, fn, obj, [obj]);
572
+ return obj;
573
+ },
574
+
575
+ /***
576
+ * @method has(<obj>, <key>)
577
+ * @returns Boolean
578
+ * @short Checks if <obj> has <key> using hasOwnProperty from Object.prototype.
579
+ * @extra This method is considered safer than %Object#hasOwnProperty% when using objects as hashes. See %http://www.devthought.com/2012/01/18/an-object-is-not-a-hash/% for more.
580
+ * @example
581
+ *
582
+ * Object.has({ foo: 'bar' }, 'foo') -> true
583
+ * Object.has({ foo: 'bar' }, 'baz') -> false
584
+ * Object.has({ hasOwnProperty: true }, 'foo') -> false
585
+ ***/
586
+ 'has': function (obj, key) {
587
+ return hasOwnProperty(obj, key);
588
+ }
589
+
590
+ });
591
+
592
+
593
+ extend(object, false, function() { return arguments.length > 1; }, {
594
+
595
+ /***
596
+ * @method keys(<obj>, [fn])
597
+ * @returns Array
598
+ * @short Returns an array containing the keys in <obj>. Optionally calls [fn] for each key.
599
+ * @extra This method is provided for browsers that don't support it natively, and additionally is enhanced to accept the callback [fn]. Returned keys are in no particular order. %keys% is available as an instance method on extended objects.
600
+ * @example
601
+ *
602
+ * Object.keys({ broken: 'wear' }) -> ['broken']
603
+ * Object.keys({ broken: 'wear' }, function(key, value) {
604
+ * // Called once for each key.
605
+ * });
606
+ * Object.extended({ broken: 'wear' }).keys() -> ['broken']
607
+ *
608
+ ***/
609
+ 'keys': function(obj, fn) {
610
+ if(obj == null || typeof obj != 'object' && !object.isRegExp(obj) && !object.isFunction(obj)) {
611
+ throw new TypeError('Object required');
612
+ }
613
+ var keys = [];
614
+ iterateOverObject(obj, function(key, value) {
615
+ keys.push(key);
616
+ if(fn) fn.call(obj, key, value);
617
+ });
618
+ return keys;
619
+ }
620
+
621
+ });
622
+
623
+
624
+
625
+
626
+
627
+
628
+
629
+
630
+ /***
631
+ * Array module
632
+ *
633
+ ***/
634
+
635
+
636
+ // Basic array internal methods
637
+
638
+ function arrayEach(arr, fn, startIndex, loop, sparse) {
639
+ var length, index, i;
640
+ checkCallback(fn);
641
+ if(startIndex < 0) startIndex = arr.length + startIndex;
642
+ i = toIntegerWithDefault(startIndex, 0);
643
+ length = loop === true ? arr.length + i : arr.length;
644
+ while(i < length) {
645
+ index = i % arr.length;
646
+ if(!(index in arr) && sparse === true) {
647
+ return iterateOverSparseArray(arr, fn, i, loop);
648
+ } else if(fn.call(arr, arr[index], index, arr) === false) {
649
+ break;
650
+ }
651
+ i++;
652
+ }
653
+ }
654
+
655
+ function arrayFind(arr, f, startIndex, loop, returnIndex) {
656
+ var result, index;
657
+ arrayEach(arr, function(el, i, arr) {
658
+ if(multiMatch(el, f, arr, [i, arr])) {
659
+ result = el;
660
+ index = i;
661
+ return false;
662
+ }
663
+ }, startIndex, loop);
664
+ return returnIndex ? index : result;
665
+ }
666
+
667
+ function arrayUnique(arr, map) {
668
+ var result = [], o = {}, stringified, transformed;
669
+ arrayEach(arr, function(el, i) {
670
+ transformed = map ? transformArgument(el, map, arr, [el, i, arr]) : el;
671
+ stringified = stringify(transformed);
672
+ if(!arrayObjectExists(o, stringified, el)) {
673
+ o[stringified] = transformed;
674
+ result.push(el);
675
+ }
676
+ })
677
+ return result;
678
+ }
679
+
680
+ function arrayFlatten(arr, level, current) {
681
+ level = level || Infinity;
682
+ current = current || 0;
683
+ var result = [];
684
+ arrayEach(arr, function(el) {
685
+ if(object.isArray(el) && current < level) {
686
+ result = result.concat(arrayFlatten(el, level, current + 1));
687
+ } else {
688
+ result.push(el);
689
+ }
690
+ });
691
+ return result;
692
+ }
693
+
694
+ function arrayIntersect(arr1, arr2, subtract) {
695
+ var result = [], o = {};
696
+ arr2.each(function(el) {
697
+ o[stringify(el)] = el;
698
+ });
699
+ arr1.each(function(el) {
700
+ var stringified = stringify(el), exists = arrayObjectExists(o, stringified, el);
701
+ // Add the result to the array if:
702
+ // 1. We're subtracting intersections or it doesn't already exist in the result and
703
+ // 2. It exists in the compared array and we're adding, or it doesn't exist and we're removing.
704
+ if(exists != subtract) {
705
+ delete o[stringified];
706
+ result.push(el);
707
+ }
708
+ });
709
+ return result;
710
+ }
711
+
712
+ function arrayObjectExists(hash, stringified, obj) {
713
+ return stringified in hash && (typeof obj !== 'function' || obj === hash[stringified]);
714
+ }
715
+
716
+ // ECMA5 methods
717
+
718
+ function arrayIndexOf(arr, search, fromIndex, increment) {
719
+ var length = arr.length,
720
+ fromRight = increment == -1,
721
+ start = fromRight ? length - 1 : 0,
722
+ index = toIntegerWithDefault(fromIndex, start);
723
+ if(index < 0) {
724
+ index = length + index;
725
+ }
726
+ if((!fromRight && index < 0) || (fromRight && index >= length)) {
727
+ index = start;
728
+ }
729
+ while((fromRight && index >= 0) || (!fromRight && index < length)) {
730
+ if(arr[index] === search) {
731
+ return index;
732
+ }
733
+ index += increment;
734
+ }
735
+ return -1;
736
+ }
737
+
738
+ function arrayReduce(arr, fn, initialValue, fromRight) {
739
+ var length = arr.length, count = 0, defined = initialValue !== Undefined, result, index;
740
+ checkCallback(fn);
741
+ if(length == 0 && !defined) {
742
+ throw new TypeError('Reduce called on empty array with no initial value');
743
+ } else if(defined) {
744
+ result = initialValue;
745
+ } else {
746
+ result = arr[fromRight ? length - 1 : count];
747
+ count++;
748
+ }
749
+ while(count < length) {
750
+ index = fromRight ? length - count - 1 : count;
751
+ if(index in arr) {
752
+ result = fn.call(Undefined, result, arr[index], index, arr);
753
+ }
754
+ count++;
755
+ }
756
+ return result;
757
+ }
758
+
759
+ function toIntegerWithDefault(i, d) {
760
+ if(isNaN(i)) {
761
+ return d;
762
+ } else {
763
+ return parseInt(i >> 0);
764
+ }
765
+ }
766
+
767
+ function isArrayIndex(arr, i) {
768
+ return i in arr && toUInt32(i) == i && i != 0xffffffff;
769
+ }
770
+
771
+ function toUInt32(i) {
772
+ return i >>> 0;
773
+ }
774
+
775
+ function checkCallback(fn) {
776
+ if(!fn || !fn.call) {
777
+ throw new TypeError('Callback is not callable');
778
+ }
779
+ }
780
+
781
+ function checkFirstArgumentExists(args) {
782
+ if(args.length === 0) {
783
+ throw new TypeError('First argument must be defined');
784
+ }
785
+ }
786
+
787
+ // Support methods
788
+
789
+ function iterateOverSparseArray(arr, fn, fromIndex, loop) {
790
+ var indexes = [], i;
791
+ for(i in arr) {
792
+ if(isArrayIndex(arr, i) && i >= fromIndex) {
793
+ indexes.push(i.toNumber());
794
+ }
795
+ }
796
+ indexes.sort().each(function(index) {
797
+ return fn.call(arr, arr[index], index, arr);
798
+ });
799
+ return arr;
800
+ }
801
+
802
+ function getMinOrMax(obj, map, which, isArray) {
803
+ var max = which === 'max', min = which === 'min';
804
+ var edge = max ? -Infinity : Infinity;
805
+ var result = [];
806
+ iterateOverObject(obj, function(key) {
807
+ var entry = obj[key];
808
+ var test = transformArgument(entry, map, obj, isArray? [entry, key.toNumber(), obj] : []);
809
+ if(test === edge) {
810
+ result.push(entry);
811
+ } else if((max && test > edge) || (min && test < edge)) {
812
+ result = [entry];
813
+ edge = test;
814
+ }
815
+ });
816
+ return result;
817
+ }
818
+
819
+
820
+ // Alphanumeric collation helpers
821
+
822
+ function collateStrings(a, b) {
823
+ var aValue, bValue, aChar, bChar, aEquiv, bEquiv, index = 0, tiebreaker = 0;
824
+ a = getCollationReadyString(a);
825
+ b = getCollationReadyString(b);
826
+ do {
827
+ aChar = getCollationCharacter(a, index);
828
+ bChar = getCollationCharacter(b, index);
829
+ aValue = getCollationValue(aChar);
830
+ bValue = getCollationValue(bChar);
831
+ if(aValue === -1 || bValue === -1) {
832
+ aValue = a.charCodeAt(index) || null;
833
+ bValue = b.charCodeAt(index) || null;
834
+ }
835
+ aEquiv = aChar !== a.charAt(index);
836
+ bEquiv = bChar !== b.charAt(index);
837
+ if(aEquiv !== bEquiv && tiebreaker === 0) {
838
+ tiebreaker = aEquiv - bEquiv;
839
+ }
840
+ index += 1;
841
+ } while(aValue != null && bValue != null && aValue === bValue);
842
+ if(aValue === bValue) return tiebreaker;
843
+ return aValue < bValue ? -1 : 1;
844
+ }
845
+
846
+ function getCollationReadyString(str) {
847
+ if(array[AlphanumericSortIgnoreCase]) {
848
+ str = str.toLowerCase();
849
+ }
850
+ return str.remove(array[AlphanumericSortIgnore]);
851
+ }
852
+
853
+ function getCollationCharacter(str, index) {
854
+ var chr = str.charAt(index), eq = array[AlphanumericSortEquivalents] || {};
855
+ return eq[chr] || chr;
856
+ }
857
+
858
+ function getCollationValue(chr) {
859
+ if(!chr) {
860
+ return null;
861
+ } else {
862
+ return array[AlphanumericSortOrder].indexOf(chr);
863
+ }
864
+ }
865
+
866
+ var AlphanumericSortOrder = 'AlphanumericSortOrder';
867
+ var AlphanumericSortIgnore = 'AlphanumericSortIgnore';
868
+ var AlphanumericSortIgnoreCase = 'AlphanumericSortIgnoreCase';
869
+ var AlphanumericSortEquivalents = 'AlphanumericSortEquivalents';
870
+
871
+ function buildArray() {
872
+ var order = 'AÁÀÂÃĄBCĆČÇDĎÐEÉÈĚÊËĘFGĞHıIÍÌİÎÏJKLŁMNŃŇÑOÓÒÔPQRŘSŚŠŞTŤUÚÙŮÛÜVWXYÝZŹŻŽÞÆŒØÕÅÄÖ';
873
+ var equiv = 'AÁÀÂÃÄ,CÇ,EÉÈÊË,IÍÌİÎÏ,OÓÒÔÕÖ,Sß,UÚÙÛÜ';
874
+ array[AlphanumericSortOrder] = order.split('').map(function(str) {
875
+ return str + str.toLowerCase();
876
+ }).join('');
877
+ var equivalents = {};
878
+ equiv.split(',').each(function(set) {
879
+ var equivalent = set.charAt(0);
880
+ set.slice(1).chars(function(chr) {
881
+ equivalents[chr] = equivalent;
882
+ equivalents[chr.toLowerCase()] = equivalent.toLowerCase();
883
+ });
884
+ });
885
+ array[AlphanumericSortIgnoreCase] = true;
886
+ array[AlphanumericSortEquivalents] = equivalents;
887
+ }
888
+
889
+ extend(array, false, false, {
890
+
891
+ /***
892
+ *
893
+ * @method Array.create(<obj1>, <obj2>, ...)
894
+ * @returns Array
895
+ * @short Alternate array constructor.
896
+ * @extra This method will create a single array by calling %concat% on all arguments passed. In addition to ensuring that an unknown variable is in a single, flat array (the standard constructor will create nested arrays, this one will not), it is also a useful shorthand to convert a function's arguments object into a standard array.
897
+ * @example
898
+ *
899
+ * Array.create('one', true, 3) -> ['one', true, 3]
900
+ * Array.create(['one', true, 3]) -> ['one', true, 3]
901
+ + Array.create(function(n) {
902
+ * return arguments;
903
+ * }('howdy', 'doody'));
904
+ *
905
+ ***/
906
+ 'create': function(obj) {
907
+ var result = [];
908
+ multiArgs(arguments, function(a) {
909
+ if(a && a.callee) a = getArgs(a);
910
+ result = result.concat(a);
911
+ });
912
+ return result;
913
+ },
914
+
915
+ /***
916
+ *
917
+ * @method Array.isArray(<obj>)
918
+ * @returns Boolean
919
+ * @short Returns true if <obj> is an Array.
920
+ * @extra This method is provided for browsers that don't support it internally.
921
+ * @example
922
+ *
923
+ * Array.isArray(3) -> false
924
+ * Array.isArray(true) -> false
925
+ * Array.isArray('wasabi') -> false
926
+ * Array.isArray([1,2,3]) -> true
927
+ *
928
+ ***/
929
+ 'isArray': function(obj) {
930
+ return isClass(obj, 'Array');
931
+ }
932
+
933
+ });
934
+
935
+
936
+
937
+ extend(array, true, function() { var a = arguments; return a.length > 0 && !object.isFunction(a[0]); }, {
938
+
939
+ /***
940
+ * @method every(<f>, [scope])
941
+ * @returns Boolean
942
+ * @short Returns true if all elements in the array match <f>.
943
+ * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts strings, numbers, deep objects, and arrays for <f>. %all% is provided an alias.
944
+ * @example
945
+ *
946
+ + ['a','a','a'].every(function(n) {
947
+ * return n == 'a';
948
+ * });
949
+ * ['a','a','a'].every('a') -> true
950
+ * [{a:2},{a:2}].every({a:2}) -> true
951
+ *
952
+ ***/
953
+ 'every': function(f, scope) {
954
+ var length = this.length, index = 0;
955
+ checkFirstArgumentExists(arguments);
956
+ while(index < length) {
957
+ if(index in this && !multiMatch(this[index], f, scope, [index, this])) {
958
+ return false;
959
+ }
960
+ index++;
961
+ }
962
+ return true;
963
+ },
964
+
965
+ /***
966
+ * @method some(<f>, [scope])
967
+ * @returns Boolean
968
+ * @short Returns true if any element in the array matches <f>.
969
+ * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts strings, numbers, deep objects, and arrays for <f>. %any% and %has% are provided as aliases.
970
+ * @example
971
+ *
972
+ + ['a','b','c'].some(function(n) {
973
+ * return n == 'a';
974
+ * });
975
+ + ['a','b','c'].some(function(n) {
976
+ * return n == 'd';
977
+ * });
978
+ * ['a','b','c'].some('a') -> true
979
+ * [{a:2},{b:5}].some({a:2}) -> true
980
+ *
981
+ ***/
982
+ 'some': function(f, scope) {
983
+ var length = this.length, index = 0;
984
+ checkFirstArgumentExists(arguments);
985
+ while(index < length) {
986
+ if(index in this && multiMatch(this[index], f, scope, [index, this])) {
987
+ return true;
988
+ }
989
+ index++;
990
+ }
991
+ return false;
992
+ },
993
+
994
+ /***
995
+ * @method map(<map>, [scope])
996
+ * @returns Array
997
+ * @short Maps the array to another array containing the values that are the result of calling <map> on each element.
998
+ * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts a string, which is a shortcut for a function that gets that property (or invokes a function) on each element. %collect% is provided as an alias.
999
+ * @example
1000
+ *
1001
+ + [1,2,3].map(function(n) {
1002
+ * return n * 3;
1003
+ * }); -> [3,6,9]
1004
+ * ['one','two','three'].map(function(n) {
1005
+ * return n.length;
1006
+ * }); -> [3,3,5]
1007
+ * ['one','two','three'].map('length') -> [3,3,5]
1008
+ *
1009
+ ***/
1010
+ 'map': function(map, scope) {
1011
+ var length = this.length, index = 0, el, result = new Array(length);
1012
+ checkFirstArgumentExists(arguments);
1013
+ while(index < length) {
1014
+ if(index in this) {
1015
+ el = this[index];
1016
+ result[index] = transformArgument(el, map, scope, [el, index, this]);
1017
+ }
1018
+ index++;
1019
+ }
1020
+ return result;
1021
+ },
1022
+
1023
+ /***
1024
+ * @method filter(<f>, [scope])
1025
+ * @returns Array
1026
+ * @short Returns any elements in the array that match <f>.
1027
+ * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts strings, numbers, deep objects, and arrays for <f>.
1028
+ * @example
1029
+ *
1030
+ + [1,2,3].filter(function(n) {
1031
+ * return n > 1;
1032
+ * });
1033
+ * [1,2,2,4].filter(2) -> 2
1034
+ *
1035
+ ***/
1036
+ 'filter': function(f, scope) {
1037
+ var length = this.length, index = 0, result = [];
1038
+ checkFirstArgumentExists(arguments);
1039
+ while(index < length) {
1040
+ if(index in this && multiMatch(this[index], f, scope, [index, this])) {
1041
+ result.push(this[index]);
1042
+ }
1043
+ index++;
1044
+ }
1045
+ return result;
1046
+ }
1047
+
1048
+ });
1049
+
1050
+
1051
+ extend(array, true, false, {
1052
+
1053
+ /***
1054
+ * @method indexOf(<search>, [fromIndex])
1055
+ * @returns Number
1056
+ * @short Searches the array and returns the first index where <search> occurs, or -1 if the element is not found.
1057
+ * @extra [fromIndex] is the index from which to begin the search. This method performs a simple strict equality comparison on <search>. It does not support enhanced functionality such as searching the contents against a regex, callback, or deep comparison of objects. For such functionality, use the %find% method instead.
1058
+ * @example
1059
+ *
1060
+ * [1,2,3].indexOf(3) -> 1
1061
+ * [1,2,3].indexOf(7) -> -1
1062
+ *
1063
+ ***/
1064
+ 'indexOf': function(search, fromIndex) {
1065
+ if(object.isString(this)) return this.indexOf(search, fromIndex);
1066
+ return arrayIndexOf(this, search, fromIndex, 1);
1067
+ },
1068
+
1069
+ /***
1070
+ * @method lastIndexOf(<search>, [fromIndex])
1071
+ * @returns Number
1072
+ * @short Searches the array and returns the last index where <search> occurs, or -1 if the element is not found.
1073
+ * @extra [fromIndex] is the index from which to begin the search. This method performs a simple strict equality comparison on <search>.
1074
+ * @example
1075
+ *
1076
+ * [1,2,1].lastIndexOf(1) -> 2
1077
+ * [1,2,1].lastIndexOf(7) -> -1
1078
+ *
1079
+ ***/
1080
+ 'lastIndexOf': function(search, fromIndex) {
1081
+ if(object.isString(this)) return this.lastIndexOf(search, fromIndex);
1082
+ return arrayIndexOf(this, search, fromIndex, -1);
1083
+ },
1084
+
1085
+ /***
1086
+ * @method forEach([fn], [scope])
1087
+ * @returns Nothing
1088
+ * @short Iterates over the array, calling [fn] on each loop.
1089
+ * @extra This method is only provided for those browsers that do not support it natively. [scope] becomes the %this% object.
1090
+ * @example
1091
+ *
1092
+ * ['a','b','c'].forEach(function(a) {
1093
+ * // Called 3 times: 'a','b','c'
1094
+ * });
1095
+ *
1096
+ ***/
1097
+ 'forEach': function(fn, scope) {
1098
+ var length = this.length, index = 0;
1099
+ checkCallback(fn);
1100
+ while(index < length) {
1101
+ if(index in this) {
1102
+ fn.call(scope, this[index], index, this);
1103
+ }
1104
+ index++;
1105
+ }
1106
+ },
1107
+
1108
+ /***
1109
+ * @method reduce([fn], [init])
1110
+ * @returns Mixed
1111
+ * @short Reduces the array to a single result.
1112
+ * @extra By default this method calls [fn] n - 1 times, where n is the length of the array. On the first call it is passed the first and second elements in the array. The result of that callback will then be passed into the next iteration until it reaches the end, where the accumulated value will be returned as the final result. If [init] is passed, it will call [fn] one extra time in the beginning passing in [init] along with the first element. This method is only provided for those browsers that do not support it natively.
1113
+ * @example
1114
+ *
1115
+ + [1,2,3,4].reduce(function(a, b) {
1116
+ * return a + b;
1117
+ * });
1118
+ + [1,2,3,4].reduce(function(a, b) {
1119
+ * return a + b;
1120
+ * }, 100);
1121
+ *
1122
+ ***/
1123
+ 'reduce': function(fn, init) {
1124
+ return arrayReduce(this, fn, init);
1125
+ },
1126
+
1127
+ /***
1128
+ * @method reduceRight([fn], [init])
1129
+ * @returns Mixed
1130
+ * @short Reduces the array to a single result by stepping through it from the right.
1131
+ * @extra By default this method calls [fn] n - 1 times, where n is the length of the array. On the first call it is passed the last and second to last elements in the array. The result of that callback will then be passed into the next iteration until it reaches the beginning, where the accumulated value will be returned as the final result. If [init] is passed, it will call [fn] one extra time in the beginning passing in [init] along with the last element. This method is only provided for those browsers that do not support it natively.
1132
+ * @example
1133
+ *
1134
+ + [1,2,3,4].reduceRight(function(a, b) {
1135
+ * return a - b;
1136
+ * });
1137
+ *
1138
+ ***/
1139
+ 'reduceRight': function(fn, init) {
1140
+ return arrayReduce(this, fn, init, true);
1141
+ },
1142
+
1143
+ /***
1144
+ * @method each(<fn>, [index] = 0, [loop] = false)
1145
+ * @returns Array
1146
+ * @short Runs <fn> against elements in the array. Enhanced version of %Array#forEach%.
1147
+ * @extra Parameters passed to <fn> are identical to %forEach%, ie. the first parameter is the current element, second parameter is the current index, and third parameter is the array itself. If <fn> returns %false% at any time it will break out of the loop. Once %each% finishes, it will return the array. If [index] is passed, <fn> will begin at that index and work its way to the end. If [loop] is true, it will then start over from the beginning of the array and continue until it reaches [index] - 1.
1148
+ * @example
1149
+ *
1150
+ * [1,2,3,4].each(function(n) {
1151
+ * // Called 4 times: 1, 2, 3, 4
1152
+ * });
1153
+ * [1,2,3,4].each(function(n) {
1154
+ * // Called 4 times: 3, 4, 1, 2
1155
+ * }, 2, true);
1156
+ *
1157
+ ***/
1158
+ 'each': function(fn, index, loop) {
1159
+ arrayEach(this, fn, index, loop, true);
1160
+ return this;
1161
+ },
1162
+
1163
+ /***
1164
+ * @method find(<f>, [index] = 0, [loop] = false)
1165
+ * @returns Mixed
1166
+ * @short Returns the first element that matches <f>.
1167
+ * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Starts at [index], and will continue once from index = 0 if [loop] is true.
1168
+ * @example
1169
+ *
1170
+ + [{a:1,b:2},{a:1,b:3},{a:1,b:4}].find(function(n) {
1171
+ * return n['a'] == 1;
1172
+ * }); -> {a:1,b:3}
1173
+ * ['cuba','japan','canada'].find(/^c/, 2) -> 'canada'
1174
+ *
1175
+ ***/
1176
+ 'find': function(f, index, loop) {
1177
+ return arrayFind(this, f, index, loop);
1178
+ },
1179
+
1180
+ /***
1181
+ * @method findAll(<f>, [index] = 0, [loop] = false)
1182
+ * @returns Array
1183
+ * @short Returns all elements that match <f>.
1184
+ * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Starts at [index], and will continue once from index = 0 if [loop] is true.
1185
+ * @example
1186
+ *
1187
+ + [{a:1,b:2},{a:1,b:3},{a:2,b:4}].findAll(function(n) {
1188
+ * return n['a'] == 1;
1189
+ * }); -> [{a:1,b:3},{a:1,b:4}]
1190
+ * ['cuba','japan','canada'].findAll(/^c/) -> 'cuba','canada'
1191
+ * ['cuba','japan','canada'].findAll(/^c/, 2) -> 'canada'
1192
+ *
1193
+ ***/
1194
+ 'findAll': function(f, index, loop) {
1195
+ var result = [];
1196
+ arrayEach(this, function(el, i, arr) {
1197
+ if(multiMatch(el, f, arr, [i, arr])) {
1198
+ result.push(el);
1199
+ }
1200
+ }, index, loop);
1201
+ return result;
1202
+ },
1203
+
1204
+ /***
1205
+ * @method findIndex(<f>, [startIndex] = 0, [loop] = false)
1206
+ * @returns Number
1207
+ * @short Returns the index of the first element that matches <f> or -1 if not found.
1208
+ * @extra This method has a few notable differences to native %indexOf%. Although <f> will similarly match a primitive such as a string or number, it will also match deep objects and arrays that are not equal by reference (%===%). Additionally, if a function is passed it will be run as a matching function (similar to the behavior of %Array#filter%) rather than attempting to find that function itself by reference in the array. Finally, a regexp will be matched against elements in the array, presumed to be strings. Starts at [index], and will continue once from index = 0 if [loop] is true.
1209
+ * @example
1210
+ *
1211
+ + [1,2,3,4].findIndex(3); -> 2
1212
+ + [1,2,3,4].findIndex(function(n) {
1213
+ * return n % 2 == 0;
1214
+ * }); -> 1
1215
+ + ['one','two','three'].findIndex(/th/); -> 2
1216
+ *
1217
+ ***/
1218
+ 'findIndex': function(f, startIndex, loop) {
1219
+ var index = arrayFind(this, f, startIndex, loop, true);
1220
+ return isUndefined(index) ? -1 : index;
1221
+ },
1222
+
1223
+ /***
1224
+ * @method count(<f>)
1225
+ * @returns Number
1226
+ * @short Counts all elements in the array that match <f>.
1227
+ * @extra <f> will match a string, number, array, object, or alternately test against a function or regex.
1228
+ * @example
1229
+ *
1230
+ * [1,2,3,1].count(1) -> 2
1231
+ * ['a','b','c'].count(/b/) -> 1
1232
+ + [{a:1},{b:2}].count(function(n) {
1233
+ * return n['a'] > 1;
1234
+ * }); -> 0
1235
+ *
1236
+ ***/
1237
+ 'count': function(f) {
1238
+ if(isUndefined(f)) return this.length;
1239
+ return this.findAll(f).length;
1240
+ },
1241
+
1242
+ /***
1243
+ * @method none(<f>)
1244
+ * @returns Boolean
1245
+ * @short Returns true if none of the elements in the array match <f>.
1246
+ * @extra <f> will match a string, number, array, object, or alternately test against a function or regex.
1247
+ * @example
1248
+ *
1249
+ * [1,2,3].none(5) -> true
1250
+ * ['a','b','c'].none(/b/) -> false
1251
+ + [{a:1},{b:2}].none(function(n) {
1252
+ * return n['a'] > 1;
1253
+ * }); -> true
1254
+ *
1255
+ ***/
1256
+ 'none': function() {
1257
+ return !this.any.apply(this, arguments);
1258
+ },
1259
+
1260
+ /***
1261
+ * @method remove([f1], [f2], ...)
1262
+ * @returns Array
1263
+ * @short Removes any element in the array that matches [f1], [f2], etc.
1264
+ * @extra Will match a string, number, array, object, or alternately test against a function or regex. This method will change the array! Use %exclude% for a non-destructive alias.
1265
+ * @example
1266
+ *
1267
+ * [1,2,3].remove(3) -> [1,2]
1268
+ * ['a','b','c'].remove(/b/) -> ['a','c']
1269
+ + [{a:1},{b:2}].remove(function(n) {
1270
+ * return n['a'] == 1;
1271
+ * }); -> [{b:2}]
1272
+ *
1273
+ ***/
1274
+ 'remove': function() {
1275
+ var i, arr = this;
1276
+ multiArgs(arguments, function(f) {
1277
+ i = 0;
1278
+ while(i < arr.length) {
1279
+ if(multiMatch(arr[i], f, arr, [i, arr])) {
1280
+ arr.splice(i, 1);
1281
+ } else {
1282
+ i++;
1283
+ }
1284
+ }
1285
+ });
1286
+ return arr;
1287
+ },
1288
+
1289
+ /***
1290
+ * @method removeAt(<start>, [end])
1291
+ * @returns Array
1292
+ * @short Removes element at <start>. If [end] is specified, removes the range between <start> and [end]. This method will change the array! If you don't intend the array to be changed use %clone% first.
1293
+ * @example
1294
+ *
1295
+ * ['a','b','c'].removeAt(0) -> ['b','c']
1296
+ * [1,2,3,4].removeAt(1, 3) -> [1]
1297
+ *
1298
+ ***/
1299
+ 'removeAt': function(start, end) {
1300
+ if(isUndefined(start)) return this;
1301
+ if(isUndefined(end)) end = start;
1302
+ for(var i = 0; i <= (end - start); i++) {
1303
+ this.splice(start, 1);
1304
+ }
1305
+ return this;
1306
+ },
1307
+
1308
+ /***
1309
+ * @method add(<el>, [index])
1310
+ * @returns Array
1311
+ * @short Adds <el> to the array.
1312
+ * @extra If [index] is specified, it will add at [index], otherwise adds to the end of the array. %add% behaves like %concat% in that if <el> is an array it will be joined, not inserted. This method will change the array! Use %include% for a non-destructive alias. Also, %insert% is provided as an alias that reads better when using an index.
1313
+ * @example
1314
+ *
1315
+ * [1,2,3,4].add(5) -> [1,2,3,4,5]
1316
+ * [1,2,3,4].add([5,6,7]) -> [1,2,3,4,5,6,7]
1317
+ * [1,2,3,4].insert(8, 1) -> [1,8,2,3,4]
1318
+ *
1319
+ ***/
1320
+ 'add': function(el, index) {
1321
+ if(!object.isNumber(number(index)) || isNaN(index) || index == -1) index = this.length;
1322
+ else if(index < -1) index += 1;
1323
+ array.prototype.splice.apply(this, [index, 0].concat(el));
1324
+ return this;
1325
+ },
1326
+
1327
+ /***
1328
+ * @method include(<el>, [index])
1329
+ * @returns Array
1330
+ * @short Adds <el> to the array.
1331
+ * @extra This is a non-destructive alias for %add%. It will not change the original array.
1332
+ * @example
1333
+ *
1334
+ * [1,2,3,4].include(5) -> [1,2,3,4,5]
1335
+ * [1,2,3,4].include(8, 1) -> [1,8,2,3,4]
1336
+ * [1,2,3,4].include([5,6,7]) -> [1,2,3,4,5,6,7]
1337
+ *
1338
+ ***/
1339
+ 'include': function(el, index) {
1340
+ return this.clone().add(el, index);
1341
+ },
1342
+
1343
+ /***
1344
+ * @method exclude([f1], [f2], ...)
1345
+ * @returns Array
1346
+ * @short Removes any element in the array that matches [f1], [f2], etc.
1347
+ * @extra This is a non-destructive alias for %remove%. It will not change the original array.
1348
+ * @example
1349
+ *
1350
+ * [1,2,3].exclude(3) -> [1,2]
1351
+ * ['a','b','c'].exclude(/b/) -> ['a','c']
1352
+ + [{a:1},{b:2}].exclude(function(n) {
1353
+ * return n['a'] == 1;
1354
+ * }); -> [{b:2}]
1355
+ *
1356
+ ***/
1357
+ 'exclude': function() {
1358
+ return array.prototype.remove.apply(this.clone(), arguments);
1359
+ },
1360
+
1361
+ /***
1362
+ * @method clone()
1363
+ * @returns Array
1364
+ * @short Clones the array.
1365
+ * @example
1366
+ *
1367
+ * [1,2,3].clone() -> [1,2,3]
1368
+ *
1369
+ ***/
1370
+ 'clone': function() {
1371
+ return object.merge([], this);
1372
+ },
1373
+
1374
+ /***
1375
+ * @method unique([map] = null)
1376
+ * @returns Array
1377
+ * @short Removes all duplicate elements in the array.
1378
+ * @extra [map] may be a function mapping the value to be uniqued on or a string acting as a shortcut. This is most commonly used when you have a key that ensures the object's uniqueness, and don't need to check all fields.
1379
+ * @example
1380
+ *
1381
+ * [1,2,2,3].unique() -> [1,2,3]
1382
+ * [{foo:'bar'},{foo:'bar'}].unique() -> [{foo:'bar'}]
1383
+ + [{foo:'bar'},{foo:'bar'}].unique(function(obj){
1384
+ * return obj.foo;
1385
+ * }); -> [{foo:'bar'}]
1386
+ * [{foo:'bar'},{foo:'bar'}].unique('foo') -> [{foo:'bar'}]
1387
+ *
1388
+ ***/
1389
+ 'unique': function(map) {
1390
+ return arrayUnique(this, map);
1391
+ },
1392
+
1393
+ /***
1394
+ * @method union([a1], [a2], ...)
1395
+ * @returns Array
1396
+ * @short Returns an array containing all elements in all arrays with duplicates removed.
1397
+ * @example
1398
+ *
1399
+ * [1,3,5].union([5,7,9]) -> [1,3,5,7,9]
1400
+ * ['a','b'].union(['b','c']) -> ['a','b','c']
1401
+ *
1402
+ ***/
1403
+ 'union': function() {
1404
+ var arr = this;
1405
+ multiArgs(arguments, function(arg) {
1406
+ arr = arr.concat(arg);
1407
+ });
1408
+ return arrayUnique(arr);
1409
+ },
1410
+
1411
+ /***
1412
+ * @method intersect([a1], [a2], ...)
1413
+ * @returns Array
1414
+ * @short Returns an array containing the elements all arrays have in common.
1415
+ * @example
1416
+ *
1417
+ * [1,3,5].intersect([5,7,9]) -> [5]
1418
+ * ['a','b'].intersect('b','c') -> ['b']
1419
+ *
1420
+ ***/
1421
+ 'intersect': function() {
1422
+ return arrayIntersect(this, multiArgs(arguments, null, true), false);
1423
+ },
1424
+
1425
+ /***
1426
+ * @method subtract([a1], [a2], ...)
1427
+ * @returns Array
1428
+ * @short Subtracts from the array all elements in [a1], [a2], etc.
1429
+ * @example
1430
+ *
1431
+ * [1,3,5].subtract([5,7,9]) -> [1,3]
1432
+ * [1,3,5].subtract([3],[5]) -> [1]
1433
+ * ['a','b'].subtract('b','c') -> ['a']
1434
+ *
1435
+ ***/
1436
+ 'subtract': function(a) {
1437
+ return arrayIntersect(this, multiArgs(arguments, null, true), true);
1438
+ },
1439
+
1440
+ /***
1441
+ * @method at(<index>, [loop] = true)
1442
+ * @returns Mixed
1443
+ * @short Gets the element(s) at a given index.
1444
+ * @extra When [loop] is true, overshooting the end of the array (or the beginning) will begin counting from the other end. As an alternate syntax, passing multiple indexes will get the elements at those indexes.
1445
+ * @example
1446
+ *
1447
+ * [1,2,3].at(0) -> 1
1448
+ * [1,2,3].at(2) -> 3
1449
+ * [1,2,3].at(4) -> 2
1450
+ * [1,2,3].at(4, false) -> null
1451
+ * [1,2,3].at(-1) -> 3
1452
+ * [1,2,3].at(0,1) -> [1,2]
1453
+ *
1454
+ ***/
1455
+ 'at': function() {
1456
+ return entryAtIndex(this, arguments);
1457
+ },
1458
+
1459
+ /***
1460
+ * @method first([num] = 1)
1461
+ * @returns Mixed
1462
+ * @short Returns the first element(s) in the array.
1463
+ * @extra When <num> is passed, returns the first <num> elements in the array.
1464
+ * @example
1465
+ *
1466
+ * [1,2,3].first() -> 1
1467
+ * [1,2,3].first(2) -> [1,2]
1468
+ *
1469
+ ***/
1470
+ 'first': function(num) {
1471
+ if(isUndefined(num)) return this[0];
1472
+ if(num < 0) num = 0;
1473
+ return this.slice(0, num);
1474
+ },
1475
+
1476
+ /***
1477
+ * @method last([num] = 1)
1478
+ * @returns Mixed
1479
+ * @short Returns the last element(s) in the array.
1480
+ * @extra When <num> is passed, returns the last <num> elements in the array.
1481
+ * @example
1482
+ *
1483
+ * [1,2,3].last() -> 3
1484
+ * [1,2,3].last(2) -> [2,3]
1485
+ *
1486
+ ***/
1487
+ 'last': function(num) {
1488
+ if(isUndefined(num)) return this[this.length - 1];
1489
+ var start = this.length - num < 0 ? 0 : this.length - num;
1490
+ return this.slice(start);
1491
+ },
1492
+
1493
+ /***
1494
+ * @method from(<index>)
1495
+ * @returns Array
1496
+ * @short Returns a slice of the array from <index>.
1497
+ * @example
1498
+ *
1499
+ * [1,2,3].from(1) -> [2,3]
1500
+ * [1,2,3].from(2) -> [3]
1501
+ *
1502
+ ***/
1503
+ 'from': function(num) {
1504
+ return this.slice(num);
1505
+ },
1506
+
1507
+ /***
1508
+ * @method to(<index>)
1509
+ * @returns Array
1510
+ * @short Returns a slice of the array up to <index>.
1511
+ * @example
1512
+ *
1513
+ * [1,2,3].to(1) -> [1]
1514
+ * [1,2,3].to(2) -> [1,2]
1515
+ *
1516
+ ***/
1517
+ 'to': function(num) {
1518
+ if(isUndefined(num)) num = this.length;
1519
+ return this.slice(0, num);
1520
+ },
1521
+
1522
+ /***
1523
+ * @method min([map])
1524
+ * @returns Array
1525
+ * @short Returns the elements in the array with the lowest value.
1526
+ * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut.
1527
+ * @example
1528
+ *
1529
+ * [1,2,3].min() -> [1]
1530
+ * ['fee','fo','fum'].min('length') -> ['fo']
1531
+ + ['fee','fo','fum'].min(function(n) {
1532
+ * return n.length;
1533
+ * }); -> ['fo']
1534
+ + [{a:3,a:2}].min(function(n) {
1535
+ * return n['a'];
1536
+ * }); -> [{a:2}]
1537
+ *
1538
+ ***/
1539
+ 'min': function(map) {
1540
+ return arrayUnique(getMinOrMax(this, map, 'min', true));
1541
+ },
1542
+
1543
+ /***
1544
+ * @method max(<map>)
1545
+ * @returns Array
1546
+ * @short Returns the elements in the array with the greatest value.
1547
+ * @extra <map> may be a function mapping the value to be checked or a string acting as a shortcut.
1548
+ * @example
1549
+ *
1550
+ * [1,2,3].max() -> [3]
1551
+ * ['fee','fo','fum'].max('length') -> ['fee','fum']
1552
+ + [{a:3,a:2}].max(function(n) {
1553
+ * return n['a'];
1554
+ * }); -> [{a:3}]
1555
+ *
1556
+ ***/
1557
+ 'max': function(map) {
1558
+ return arrayUnique(getMinOrMax(this, map, 'max', true));
1559
+ },
1560
+
1561
+ /***
1562
+ * @method least(<map>)
1563
+ * @returns Array
1564
+ * @short Returns the elements in the array with the least commonly occuring value.
1565
+ * @extra <map> may be a function mapping the value to be checked or a string acting as a shortcut.
1566
+ * @example
1567
+ *
1568
+ * [3,2,2].least() -> [3]
1569
+ * ['fe','fo','fum'].least('length') -> ['fum']
1570
+ + [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].least(function(n) {
1571
+ * return n.age;
1572
+ * }); -> [{age:35,name:'ken'}]
1573
+ *
1574
+ ***/
1575
+ 'least': function() {
1576
+ var result = arrayFlatten(getMinOrMax(this.groupBy.apply(this, arguments), 'length', 'min'));
1577
+ return result.length === this.length ? [] : arrayUnique(result);
1578
+ },
1579
+
1580
+ /***
1581
+ * @method most(<map>)
1582
+ * @returns Array
1583
+ * @short Returns the elements in the array with the most commonly occuring value.
1584
+ * @extra <map> may be a function mapping the value to be checked or a string acting as a shortcut.
1585
+ * @example
1586
+ *
1587
+ * [3,2,2].most() -> [2]
1588
+ * ['fe','fo','fum'].most('length') -> ['fe','fo']
1589
+ + [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].most(function(n) {
1590
+ * return n.age;
1591
+ * }); -> [{age:12,name:'bob'},{age:12,name:'ted'}]
1592
+ *
1593
+ ***/
1594
+ 'most': function() {
1595
+ var result = arrayFlatten(getMinOrMax(this.groupBy.apply(this, arguments), 'length', 'max'));
1596
+ return result.length === this.length ? [] : arrayUnique(result);
1597
+ },
1598
+
1599
+ /***
1600
+ * @method sum(<map>)
1601
+ * @returns Number
1602
+ * @short Sums all values in the array.
1603
+ * @extra <map> may be a function mapping the value to be summed or a string acting as a shortcut.
1604
+ * @example
1605
+ *
1606
+ * [1,2,2].sum() -> 5
1607
+ + [{age:35},{age:12},{age:12}].sum(function(n) {
1608
+ * return n.age;
1609
+ * }); -> 59
1610
+ * [{age:35},{age:12},{age:12}].sum('age') -> 59
1611
+ *
1612
+ ***/
1613
+ 'sum': function(map) {
1614
+ var arr = map ? this.map(map) : this;
1615
+ return arr.length > 0 ? arr.reduce(function(a,b) { return a + b; }) : 0;
1616
+ },
1617
+
1618
+ /***
1619
+ * @method average(<map>)
1620
+ * @returns Number
1621
+ * @short Averages all values in the array.
1622
+ * @extra <map> may be a function mapping the value to be averaged or a string acting as a shortcut.
1623
+ * @example
1624
+ *
1625
+ * [1,2,3].average() -> 2
1626
+ + [{age:35},{age:11},{age:11}].average(function(n) {
1627
+ * return n.age;
1628
+ * }); -> 19
1629
+ * [{age:35},{age:11},{age:11}].average('age') -> 19
1630
+ *
1631
+ ***/
1632
+ 'average': function(map) {
1633
+ var arr = map ? this.map(map) : this;
1634
+ return arr.length > 0 ? arr.sum() / arr.length : 0;
1635
+ },
1636
+
1637
+ /***
1638
+ * @method groupBy(<map>, [fn])
1639
+ * @returns Object
1640
+ * @short Groups the array by <map>.
1641
+ * @extra Will return an object with keys equal to the grouped values. <map> may be a mapping function, or a string acting as a shortcut. Optionally calls [fn] for each group.
1642
+ * @example
1643
+ *
1644
+ * ['fee','fi','fum'].groupBy('length') -> { 2: ['fi'], 3: ['fee','fum'] }
1645
+ + [{age:35,name:'ken'},{age:15,name:'bob'}].groupBy(function(n) {
1646
+ * return n.age;
1647
+ * }); -> { 35: [{age:35,name:'ken'}], 15: [{age:15,name:'bob'}] }
1648
+ *
1649
+ ***/
1650
+ 'groupBy': function(map, fn) {
1651
+ var arr = this, result = object.extended(), key;
1652
+ arrayEach(arr, function(el, index) {
1653
+ key = transformArgument(el, map, arr, [el, index, arr]);
1654
+ if(!result[key]) result[key] = [];
1655
+ result[key].push(el);
1656
+ });
1657
+ return result.each(fn);
1658
+ },
1659
+
1660
+ /***
1661
+ * @method inGroups(<num>, [padding])
1662
+ * @returns Array
1663
+ * @short Groups the array into <num> arrays.
1664
+ * @extra [padding] specifies a value with which to pad the last array so that they are all equal length.
1665
+ * @example
1666
+ *
1667
+ * [1,2,3,4,5,6,7].inGroups(3) -> [ [1,2,3], [4,5,6], [7] ]
1668
+ * [1,2,3,4,5,6,7].inGroups(3, 'none') -> [ [1,2,3], [4,5,6], [7,'none','none'] ]
1669
+ *
1670
+ ***/
1671
+ 'inGroups': function(num, padding) {
1672
+ var pad = arguments.length > 1;
1673
+ var arr = this;
1674
+ var result = [];
1675
+ var divisor = (this.length / num).ceil();
1676
+ (0).upto(num - 1, function(i) {
1677
+ var index = i * divisor;
1678
+ var group = arr.slice(index, index + divisor);
1679
+ if(pad && group.length < divisor) {
1680
+ (divisor - group.length).times(function() {
1681
+ group = group.add(padding);
1682
+ });
1683
+ }
1684
+ result.push(group);
1685
+ });
1686
+ return result;
1687
+ },
1688
+
1689
+ /***
1690
+ * @method inGroupsOf(<num>, [padding] = null)
1691
+ * @returns Array
1692
+ * @short Groups the array into arrays of <num> elements each.
1693
+ * @extra [padding] specifies a value with which to pad the last array so that they are all equal length.
1694
+ * @example
1695
+ *
1696
+ * [1,2,3,4,5,6,7].inGroupsOf(4) -> [ [1,2,3,4], [5,6,7] ]
1697
+ * [1,2,3,4,5,6,7].inGroupsOf(4, 'none') -> [ [1,2,3,4], [5,6,7,'none'] ]
1698
+ *
1699
+ ***/
1700
+ 'inGroupsOf': function(num, padding) {
1701
+ if(this.length === 0 || num === 0) return this;
1702
+ if(isUndefined(num)) num = 1;
1703
+ if(isUndefined(padding)) padding = null;
1704
+ var result = [];
1705
+ var group = null;
1706
+ var len = this.length;
1707
+ this.each(function(el, i) {
1708
+ if((i % num) === 0) {
1709
+ if(group) result.push(group);
1710
+ group = [];
1711
+ }
1712
+ if(isUndefined(el)) el = padding;
1713
+ group.push(el);
1714
+ });
1715
+ if(!this.length.isMultipleOf(num)) {
1716
+ (num - (this.length % num)).times(function() {
1717
+ group.push(padding);
1718
+ });
1719
+ this.length = this.length + (num - (this.length % num));
1720
+ }
1721
+ if(group.length > 0) result.push(group);
1722
+ return result;
1723
+ },
1724
+
1725
+ /***
1726
+ * @method compact([all] = false)
1727
+ * @returns Array
1728
+ * @short Removes all instances of %undefined%, %null%, and %NaN% from the array.
1729
+ * @extra If [all] is %true%, all "falsy" elements will be removed. This includes empty strings, 0, and false.
1730
+ * @example
1731
+ *
1732
+ * [1,null,2,undefined,3].compact() -> [1,2,3]
1733
+ * [1,'',2,false,3].compact() -> [1,'',2,false,3]
1734
+ * [1,'',2,false,3].compact(true) -> [1,2,3]
1735
+ *
1736
+ ***/
1737
+ 'compact': function(all) {
1738
+ var result = [];
1739
+ arrayEach(this, function(el, i) {
1740
+ if(object.isArray(el)) {
1741
+ result.push(el.compact());
1742
+ } else if(all && el) {
1743
+ result.push(el);
1744
+ } else if(!all && el != null && !object.isNaN(el)) {
1745
+ result.push(el);
1746
+ }
1747
+ });
1748
+ return result;
1749
+ },
1750
+
1751
+ /***
1752
+ * @method isEmpty()
1753
+ * @returns Boolean
1754
+ * @short Returns true if the array is empty.
1755
+ * @extra This is true if the array has a length of zero, or contains only %undefined%, %null%, or %NaN%.
1756
+ * @example
1757
+ *
1758
+ * [].isEmpty() -> true
1759
+ * [null,undefined].isEmpty() -> true
1760
+ *
1761
+ ***/
1762
+ 'isEmpty': function() {
1763
+ return this.compact().length == 0;
1764
+ },
1765
+
1766
+ /***
1767
+ * @method flatten([limit] = Infinity)
1768
+ * @returns Array
1769
+ * @short Returns a flattened, one-dimensional copy of the array.
1770
+ * @extra You can optionally specify a [limit], which will only flatten that depth.
1771
+ * @example
1772
+ *
1773
+ * [[1], 2, [3]].flatten() -> [1,2,3]
1774
+ * [['a'],[],'b','c'].flatten() -> ['a','b','c']
1775
+ *
1776
+ ***/
1777
+ 'flatten': function(limit) {
1778
+ return arrayFlatten(this, limit);
1779
+ },
1780
+
1781
+ /***
1782
+ * @method sortBy(<map>, [desc] = false)
1783
+ * @returns Array
1784
+ * @short Sorts the array by <map>.
1785
+ * @extra <map> may be a function, a string acting as a shortcut, or blank (direct comparison of array values). [desc] will sort the array in descending order. When the field being sorted on is a string, the resulting order will be determined by an internal algorithm that is optimized for major Western languages, but can be customized. For more information see @array_sorting.
1786
+ * @example
1787
+ *
1788
+ * ['world','a','new'].sortBy('length') -> ['a','new','world']
1789
+ * ['world','a','new'].sortBy('length', true) -> ['world','new','a']
1790
+ + [{age:72},{age:13},{age:18}].sortBy(function(n) {
1791
+ * return n.age;
1792
+ * }); -> [{age:13},{age:18},{age:72}]
1793
+ *
1794
+ ***/
1795
+ 'sortBy': function(map, desc) {
1796
+ var arr = this.clone();
1797
+ arr.sort(function(a, b) {
1798
+ var aProperty, bProperty, comp;
1799
+ aProperty = transformArgument(a, map, arr, [a]);
1800
+ bProperty = transformArgument(b, map, arr, [b]);
1801
+ if(object.isString(aProperty) && object.isString(bProperty)) {
1802
+ comp = collateStrings(aProperty, bProperty);
1803
+ } else if(aProperty < bProperty) {
1804
+ comp = -1;
1805
+ } else if(aProperty > bProperty) {
1806
+ comp = 1;
1807
+ } else {
1808
+ comp = 0;
1809
+ }
1810
+ return comp * (desc ? -1 : 1);
1811
+ });
1812
+ return arr;
1813
+ },
1814
+
1815
+ /***
1816
+ * @method randomize()
1817
+ * @returns Array
1818
+ * @short Randomizes the array.
1819
+ * @extra Uses Fisher-Yates algorithm.
1820
+ * @example
1821
+ *
1822
+ * [1,2,3,4].randomize() -> [?,?,?,?]
1823
+ *
1824
+ ***/
1825
+ 'randomize': function() {
1826
+ var a = this.concat();
1827
+ for(var j, x, i = a.length; i; j = parseInt(Math.random() * i), x = a[--i], a[i] = a[j], a[j] = x) {};
1828
+ return a;
1829
+ },
1830
+
1831
+ /***
1832
+ * @method zip([arr1], [arr2], ...)
1833
+ * @returns Array
1834
+ * @short Merges multiple arrays together.
1835
+ * @extra This method "zips up" smaller arrays into one large whose elements are "all elements at index 0", "all elements at index 1", etc. Useful when you have associated data that is split over separated arrays. If the arrays passed have more elements than the original array, they will be discarded. If they have fewer elements, the missing elements will filled with %null%.
1836
+ * @example
1837
+ *
1838
+ * [1,2,3].zip([4,5,6]) -> [[1,2], [3,4], [5,6]]
1839
+ * ['Martin','John'].zip(['Luther','F.'], ['King','Kennedy']) -> [['Martin','Luther','King'], ['John','F.','Kennedy']]
1840
+ *
1841
+ ***/
1842
+ 'zip': function() {
1843
+ var args = getArgs(arguments);
1844
+ return this.map(function(el, i) {
1845
+ return [el].concat(args.map(function(k) {
1846
+ return (i in k) ? k[i] : null;
1847
+ }));
1848
+ });
1849
+ },
1850
+
1851
+ /***
1852
+ * @method sample([num] = null)
1853
+ * @returns Mixed
1854
+ * @short Returns a random element from the array.
1855
+ * @extra If [num] is a number greater than 0, will return an array containing [num] samples.
1856
+ * @example
1857
+ *
1858
+ * [1,2,3,4,5].sample() -> // Random element
1859
+ * [1,2,3,4,5].sample(3) -> // Array of 3 random elements
1860
+ *
1861
+ ***/
1862
+ 'sample': function(num) {
1863
+ var result = [], arr = this.clone(), index;
1864
+ if(!(num > 0)) num = 1;
1865
+ while(result.length < num) {
1866
+ index = Number.random(0, arr.length - 1);
1867
+ result.push(arr[index]);
1868
+ arr.removeAt(index);
1869
+ if(arr.length == 0) break;
1870
+ }
1871
+ return arguments.length > 0 ? result : result[0];
1872
+ }
1873
+
1874
+ });
1875
+
1876
+
1877
+ // Aliases
1878
+ extend(array, true, false, {
1879
+
1880
+ /***
1881
+ * @method all()
1882
+ * @alias every
1883
+ *
1884
+ ***/
1885
+ 'all': array.prototype.every,
1886
+
1887
+ /*** @method any()
1888
+ * @alias some
1889
+ *
1890
+ ***/
1891
+ 'any': array.prototype.some,
1892
+
1893
+ /***
1894
+ * @method has()
1895
+ * @alias some
1896
+ *
1897
+ ***/
1898
+ 'has': array.prototype.some,
1899
+
1900
+ /***
1901
+ * @method insert()
1902
+ * @alias add
1903
+ *
1904
+ ***/
1905
+ 'insert': array.prototype.add
1906
+
1907
+ });
1908
+
1909
+
1910
+
1911
+
1912
+
1913
+
1914
+
1915
+
1916
+
1917
+
1918
+ /***
1919
+ * Number module
1920
+ *
1921
+ ***/
1922
+
1923
+
1924
+ function round(val, precision, method) {
1925
+ var fn = Math[method || 'round'];
1926
+ var multiplier = Math.pow(10, (precision || 0).abs());
1927
+ if(precision < 0) multiplier = 1 / multiplier;
1928
+ return fn(val * multiplier) / multiplier;
1929
+ }
1930
+
1931
+ function getRange(start, stop, fn, step) {
1932
+ var arr = [], i = parseInt(start), up = step > 0;
1933
+ while((up && i <= stop) || (!up && i >= stop)) {
1934
+ arr.push(i);
1935
+ if(fn) fn.call(this, i);
1936
+ i += step;
1937
+ }
1938
+ return arr;
1939
+ }
1940
+
1941
+ function abbreviateNumber(num, roundTo, str, mid, limit, bytes) {
1942
+ var fixed = num.toFixed(20),
1943
+ decimalPlace = fixed.search(/\./),
1944
+ numeralPlace = fixed.search(/[1-9]/),
1945
+ significant = decimalPlace - numeralPlace,
1946
+ unit, i, divisor;
1947
+ if(significant > 0) {
1948
+ significant -= 1;
1949
+ }
1950
+ i = Math.max(Math.min((significant / 3).floor(), limit === false ? str.length : limit), -mid);
1951
+ unit = str.charAt(i + mid - 1);
1952
+ if(significant < -9) {
1953
+ i = -3;
1954
+ roundTo = significant.abs() - 9;
1955
+ unit = str.first();
1956
+ }
1957
+ divisor = bytes ? (2).pow(10 * i) : (10).pow(i * 3);
1958
+ return (num / divisor).round(roundTo || 0).format() + unit.trim();
1959
+ }
1960
+
1961
+
1962
+ extend(number, false, false, {
1963
+
1964
+ /***
1965
+ * @method Number.random([n1], [n2])
1966
+ * @returns Number
1967
+ * @short Returns a random integer between [n1] and [n2].
1968
+ * @extra If only 1 number is passed, the other will be 0. If none are passed, the number will be either 0 or 1.
1969
+ * @example
1970
+ *
1971
+ * Number.random(50, 100) -> ex. 85
1972
+ * Number.random(50) -> ex. 27
1973
+ * Number.random() -> ex. 0
1974
+ *
1975
+ ***/
1976
+ 'random': function(n1, n2) {
1977
+ var min, max;
1978
+ if(arguments.length == 1) n2 = n1, n1 = 0;
1979
+ min = Math.min(n1 || 0, isUndefined(n2) ? 1 : n2);
1980
+ max = Math.max(n1 || 0, isUndefined(n2) ? 1 : n2);
1981
+ return round((Math.random() * (max - min)) + min);
1982
+ }
1983
+
1984
+ });
1985
+
1986
+ extend(number, true, false, {
1987
+
1988
+ /***
1989
+ * @method toNumber()
1990
+ * @returns Number
1991
+ * @short Returns a number. This is mostly for compatibility reasons.
1992
+ * @example
1993
+ *
1994
+ * (420).toNumber() -> 420
1995
+ *
1996
+ ***/
1997
+ 'toNumber': function() {
1998
+ return parseFloat(this, 10);
1999
+ },
2000
+
2001
+ /***
2002
+ * @method abbr([precision] = 0)
2003
+ * @returns String
2004
+ * @short Returns an abbreviated form of the number.
2005
+ * @extra [precision] will round to the given precision.
2006
+ * @example
2007
+ *
2008
+ * (1000).abbr() -> "1k"
2009
+ * (1000000).abbr() -> "1m"
2010
+ * (1280).abbr(1) -> "1.3k"
2011
+ *
2012
+ ***/
2013
+ 'abbr': function(precision) {
2014
+ return abbreviateNumber(this, precision, 'kmbt', 0, 4);
2015
+ },
2016
+
2017
+ /***
2018
+ * @method metric([precision] = 0, [limit] = 1)
2019
+ * @returns String
2020
+ * @short Returns the number as a string in metric notation.
2021
+ * @extra [precision] will round to the given precision. Both very large numbers and very small numbers are supported. [limit] is the upper limit for the units. The default is %1%, which is "kilo". If [limit] is %false%, the upper limit will be "exa". The lower limit is "nano", and cannot be changed.
2022
+ * @example
2023
+ *
2024
+ * (1000).metric() -> "1k"
2025
+ * (1000000).metric() -> "1,000k"
2026
+ * (1000000).metric(0, false) -> "1M"
2027
+ * (1249).metric(2) + 'g' -> "1.25kg"
2028
+ * (0.025).metric() + 'm' -> "25mm"
2029
+ *
2030
+ ***/
2031
+ 'metric': function(precision, limit) {
2032
+ return abbreviateNumber(this, precision, 'nμm kMGTPE', 4, isUndefined(limit) ? 1 : limit);
2033
+ },
2034
+
2035
+ /***
2036
+ * @method bytes([precision] = 0, [limit] = 4)
2037
+ * @returns String
2038
+ * @short Returns an abbreviated form of the number, considered to be "Bytes".
2039
+ * @extra [precision] will round to the given precision. [limit] is the upper limit for the units. The default is %4%, which is "terabytes" (TB). If [limit] is %false%, the upper limit will be "exa".
2040
+ * @example
2041
+ *
2042
+ * (1000).bytes() -> "1kB"
2043
+ * (1000).bytes(2) -> "0.98kB"
2044
+ * ((10).pow(20)).bytes() -> "90,949,470TB"
2045
+ * ((10).pow(20)).bytes(0, false) -> "87EB"
2046
+ *
2047
+ ***/
2048
+ 'bytes': function(precision, limit) {
2049
+ return abbreviateNumber(this, precision, 'kMGTPE', 0, isUndefined(limit) ? 4 : limit, true) + 'B';
2050
+ },
2051
+
2052
+ /***
2053
+ * @method isInteger()
2054
+ * @returns Boolean
2055
+ * @short Returns true if the number has no trailing decimal.
2056
+ * @example
2057
+ *
2058
+ * (420).isInteger() -> true
2059
+ * (4.5).isInteger() -> false
2060
+ *
2061
+ ***/
2062
+ 'isInteger': function() {
2063
+ return this % 1 == 0;
2064
+ },
2065
+
2066
+ /***
2067
+ * @method ceil([precision] = 0)
2068
+ * @returns Number
2069
+ * @short Rounds the number up. [precision] will round to the given precision.
2070
+ * @example
2071
+ *
2072
+ * (4.434).ceil() -> 5
2073
+ * (-4.434).ceil() -> -4
2074
+ * (44.17).ceil(1) -> 44.2
2075
+ * (4417).ceil(-2) -> 4500
2076
+ *
2077
+ ***/
2078
+ 'ceil': function(precision) {
2079
+ return round(this, precision, 'ceil');
2080
+ },
2081
+
2082
+ /***
2083
+ * @method floor([precision] = 0)
2084
+ * @returns Number
2085
+ * @short Rounds the number down. [precision] will round to the given precision.
2086
+ * @example
2087
+ *
2088
+ * (4.434).floor() -> 4
2089
+ * (-4.434).floor() -> -5
2090
+ * (44.17).floor(1) -> 44.1
2091
+ * (4417).floor(-2) -> 4400
2092
+ *
2093
+ ***/
2094
+ 'floor': function(precision) {
2095
+ return round(this, precision, 'floor');
2096
+ },
2097
+
2098
+ /***
2099
+ * @method abs()
2100
+ * @returns Number
2101
+ * @short Returns the absolute value for the number.
2102
+ * @example
2103
+ *
2104
+ * (3).abs() -> 3
2105
+ * (-3).abs() -> 3
2106
+ *
2107
+ ***/
2108
+ 'abs': function() {
2109
+ return Math.abs(this);
2110
+ },
2111
+
2112
+ /***
2113
+ * @method pow(<p> = 1)
2114
+ * @returns Number
2115
+ * @short Returns the number to the power of <p>.
2116
+ * @example
2117
+ *
2118
+ * (3).pow(2) -> 9
2119
+ * (3).pow(3) -> 27
2120
+ * (3).pow() -> 3
2121
+ *
2122
+ ***/
2123
+ 'pow': function(power) {
2124
+ if(isUndefined(power)) power = 1;
2125
+ return Math.pow(this, power);
2126
+ },
2127
+
2128
+ /***
2129
+ * @method round(<precision> = 0)
2130
+ * @returns Number
2131
+ * @short Rounds a number to the precision of <precision>.
2132
+ * @example
2133
+ *
2134
+ * (3.241).round() -> 3
2135
+ * (3.841).round() -> 4
2136
+ * (-3.241).round() -> -3
2137
+ * (-3.841).round() -> -4
2138
+ * (3.241).round(2) -> 3.24
2139
+ * (3748).round(-2) -> 3800
2140
+ *
2141
+ ***/
2142
+ 'round': function(precision) {
2143
+ return round(this, precision, 'round');
2144
+ },
2145
+
2146
+ /***
2147
+ * @method chr()
2148
+ * @returns String
2149
+ * @short Returns a string at the code point of the number.
2150
+ * @example
2151
+ *
2152
+ * (65).chr() -> "A"
2153
+ * (75).chr() -> "K"
2154
+ *
2155
+ ***/
2156
+ 'chr': function() {
2157
+ return string.fromCharCode(this);
2158
+ },
2159
+
2160
+ /***
2161
+ * @method isOdd()
2162
+ * @returns Boolean
2163
+ * @short Returns true if the number is odd.
2164
+ * @example
2165
+ *
2166
+ * (3).isOdd() -> true
2167
+ * (18).isOdd() -> false
2168
+ *
2169
+ ***/
2170
+ 'isOdd': function() {
2171
+ return !this.isMultipleOf(2);
2172
+ },
2173
+
2174
+ /***
2175
+ * @method isEven()
2176
+ * @returns Boolean
2177
+ * @short Returns true if the number is even.
2178
+ * @example
2179
+ *
2180
+ * (6).isEven() -> true
2181
+ * (17).isEven() -> false
2182
+ *
2183
+ ***/
2184
+ 'isEven': function() {
2185
+ return this.isMultipleOf(2);
2186
+ },
2187
+
2188
+ /***
2189
+ * @method isMultipleOf(<num>)
2190
+ * @returns Boolean
2191
+ * @short Returns true if the number is a multiple of <num>.
2192
+ * @example
2193
+ *
2194
+ * (6).isMultipleOf(2) -> true
2195
+ * (17).isMultipleOf(2) -> false
2196
+ * (32).isMultipleOf(4) -> true
2197
+ * (34).isMultipleOf(4) -> false
2198
+ *
2199
+ ***/
2200
+ 'isMultipleOf': function(num) {
2201
+ return this % num === 0;
2202
+ },
2203
+
2204
+ /***
2205
+ * @method upto(<num>, [fn], [step] = 1)
2206
+ * @returns Array
2207
+ * @short Returns an array containing numbers from the number up to <num>.
2208
+ * @extra Optionally calls [fn] callback for each number in that array. [step] allows multiples greater than 1.
2209
+ * @example
2210
+ *
2211
+ * (2).upto(6) -> [2, 3, 4, 5, 6]
2212
+ * (2).upto(6, function(n) {
2213
+ * // This function is called 5 times receiving n as the value.
2214
+ * });
2215
+ * (2).upto(8, null, 2) -> [2, 4, 6, 8]
2216
+ *
2217
+ ***/
2218
+ 'upto': function(num, fn, step) {
2219
+ return getRange(this, num, fn, step || 1);
2220
+ },
2221
+
2222
+ /***
2223
+ * @method downto(<num>, [fn], [step] = 1)
2224
+ * @returns Array
2225
+ * @short Returns an array containing numbers from the number down to <num>.
2226
+ * @extra Optionally calls [fn] callback for each number in that array. [step] allows multiples greater than 1.
2227
+ * @example
2228
+ *
2229
+ * (8).downto(3) -> [8, 7, 6, 5, 4, 3]
2230
+ * (8).downto(3, function(n) {
2231
+ * // This function is called 6 times receiving n as the value.
2232
+ * });
2233
+ * (8).downto(2, null, 2) -> [8, 6, 4, 2]
2234
+ *
2235
+ ***/
2236
+ 'downto': function(num, fn, step) {
2237
+ return getRange(this, num, fn, -(step || 1));
2238
+ },
2239
+
2240
+
2241
+ /***
2242
+ * @method times(<fn>)
2243
+ * @returns Number
2244
+ * @short Calls <fn> a number of times equivalent to the number.
2245
+ * @example
2246
+ *
2247
+ * (8).times(function(i) {
2248
+ * // This function is called 8 times.
2249
+ * });
2250
+ *
2251
+ ***/
2252
+ 'times': function(fn) {
2253
+ if(fn) {
2254
+ for(var i = 0; i < this; i++) {
2255
+ fn.call(this, i);
2256
+ }
2257
+ }
2258
+ return this.toNumber();
2259
+ },
2260
+
2261
+ /***
2262
+ * @method ordinalize()
2263
+ * @returns String
2264
+ * @short Returns an ordinalized (English) string, i.e. "1st", "2nd", etc.
2265
+ * @example
2266
+ *
2267
+ * (1).ordinalize() -> '1st';
2268
+ * (2).ordinalize() -> '2nd';
2269
+ * (8).ordinalize() -> '8th';
2270
+ *
2271
+ ***/
2272
+ 'ordinalize': function() {
2273
+ var suffix, num = this.abs(), last = num.toString().last(2).toNumber();
2274
+ if(last >= 11 && last <= 13) {
2275
+ suffix = 'th';
2276
+ } else {
2277
+ switch(num % 10) {
2278
+ case 1: suffix = 'st'; break;
2279
+ case 2: suffix = 'nd'; break;
2280
+ case 3: suffix = 'rd'; break;
2281
+ default: suffix = 'th';
2282
+ }
2283
+ }
2284
+ return this.toString() + suffix;
2285
+ },
2286
+
2287
+
2288
+ /***
2289
+ * @method pad(<place> = 0, [sign] = false, [base] = 10)
2290
+ * @returns String
2291
+ * @short Pads a number with "0" to <place>.
2292
+ * @extra [sign] allows you to force the sign as well (+05, etc). [base] can change the base for numeral conversion.
2293
+ * @example
2294
+ *
2295
+ * (5).pad(2) -> '05'
2296
+ * (-5).pad(4) -> '-0005'
2297
+ * (82).pad(3, true) -> '+082'
2298
+ *
2299
+ ***/
2300
+ 'pad': function(place, sign, base) {
2301
+ base = base || 10;
2302
+ var str = this.toNumber() === 0 ? '' : this.toString(base).replace(/^-/, '');
2303
+ str = padString(str, '0', place - str.replace(/\.\d+$/, '').length, 0);
2304
+ if(sign || this < 0) {
2305
+ str = (this < 0 ? '-' : '+') + str;
2306
+ }
2307
+ return str;
2308
+ },
2309
+
2310
+ /***
2311
+ * @method format([place] = 0, [thousands] = ',', [decimal] = '.')
2312
+ * @returns String
2313
+ * @short Formats the number to a readable string.
2314
+ * @extra If [place] is %undefined%, will automatically determine the place. [thousands] is the character used for the thousands separator. [decimal] is the character used for the decimal point.
2315
+ * @example
2316
+ *
2317
+ * (56782).format() -> '56,782'
2318
+ * (56782).format(2) -> '56,782.00'
2319
+ * (4388.43).format(2, ' ') -> '4 388.43'
2320
+ * (4388.43).format(2, '.', ',') -> '4.388,43'
2321
+ *
2322
+ ***/
2323
+ 'format': function(place, thousands, decimal) {
2324
+ var str, split, method, after, r = /(\d+)(\d{3})/;
2325
+ if(string(thousands).match(/\d/)) throw new TypeError('Thousands separator cannot contain numbers.');
2326
+ str = object.isNumber(place) ? round(this, place).toFixed(Math.max(place, 0)) : this.toString();
2327
+ thousands = thousands || ',';
2328
+ decimal = decimal || '.';
2329
+ split = str.split('.');
2330
+ str = split[0];
2331
+ after = split[1] || '';
2332
+ while (str.match(r)) {
2333
+ str = str.replace(r, '$1' + thousands + '$2');
2334
+ }
2335
+ if(after.length > 0) {
2336
+ str += decimal + padString(after, '0', 0, place - after.length);
2337
+ }
2338
+ return str;
2339
+ },
2340
+
2341
+ /***
2342
+ * @method hex([pad] = 1)
2343
+ * @returns String
2344
+ * @short Converts the number to hexidecimal.
2345
+ * @extra [pad] will pad the resulting string to that many places.
2346
+ * @example
2347
+ *
2348
+ * (255).hex() -> 'ff';
2349
+ * (255).hex(4) -> '00ff';
2350
+ * (23654).hex() -> '5c66';
2351
+ *
2352
+ ***/
2353
+ 'hex': function(pad) {
2354
+ return this.pad(pad || 1, false, 16);
2355
+ }
2356
+
2357
+ });
2358
+
2359
+
2360
+
2361
+
2362
+
2363
+ /***
2364
+ * String module
2365
+ *
2366
+ ***/
2367
+
2368
+
2369
+ // WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in the Space, Separator category.
2370
+ var getTrimmableCharacters = function() {
2371
+ return '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF';
2372
+ }
2373
+
2374
+ /***
2375
+ * @method has[Script]()
2376
+ * @returns Boolean
2377
+ * @short Returns true if the string contains any characters in that script.
2378
+ * @example
2379
+ *
2380
+ * 'أتكلم'.hasArabic() -> true
2381
+ * 'визит'.hasCyrillic() -> true
2382
+ * '잘 먹겠습니다!'.hasHangul() -> true
2383
+ * 'ミックスです'.hasKatakana() -> true
2384
+ * "l'année".hasLatin() -> true
2385
+ *
2386
+ ***
2387
+ * @method is[Script]()
2388
+ * @returns Boolean
2389
+ * @short Returns true if the string contains only characters in that script. Whitespace is ignored.
2390
+ * @example
2391
+ *
2392
+ * 'أتكلم'.isArabic() -> true
2393
+ * 'визит'.isCyrillic() -> true
2394
+ * '잘 먹겠습니다!'.isHangul() -> true
2395
+ * 'ミックスです'.isKatakana() -> false
2396
+ * "l'année".isLatin() -> true
2397
+ *
2398
+ ***
2399
+ * @method hasArabic()
2400
+ * @set hasScript
2401
+ ***
2402
+ * @method isArabic()
2403
+ * @set isScript
2404
+ ****
2405
+ * @method hasCyrillic()
2406
+ * @set hasScript
2407
+ ***
2408
+ * @method isCyrillic()
2409
+ * @set isScript
2410
+ ****
2411
+ * @method hasGreek()
2412
+ * @set hasScript
2413
+ ***
2414
+ * @method isGreek()
2415
+ * @set isScript
2416
+ ****
2417
+ * @method hasHangul()
2418
+ * @set hasScript
2419
+ ***
2420
+ * @method isHangul()
2421
+ * @set isScript
2422
+ ****
2423
+ * @method hasHan()
2424
+ * @set hasScript
2425
+ ***
2426
+ * @method isHan()
2427
+ * @set isScript
2428
+ ****
2429
+ * @method hasKanji()
2430
+ * @set hasScript
2431
+ ***
2432
+ * @method isKanji()
2433
+ * @set isScript
2434
+ ****
2435
+ * @method hasHebrew()
2436
+ * @set hasScript
2437
+ ***
2438
+ * @method isHebrew()
2439
+ * @set isScript
2440
+ ****
2441
+ * @method hasHiragana()
2442
+ * @set hasScript
2443
+ ***
2444
+ * @method isHiragana()
2445
+ * @set isScript
2446
+ ****
2447
+ * @method hasKana()
2448
+ * @set hasScript
2449
+ ***
2450
+ * @method isKana()
2451
+ * @set isScript
2452
+ ****
2453
+ * @method hasKatakana()
2454
+ * @set hasScript
2455
+ ***
2456
+ * @method isKatakana()
2457
+ * @set isScript
2458
+ ****
2459
+ * @method hasLatin()
2460
+ * @set hasScript
2461
+ ***
2462
+ * @method isKatakana()
2463
+ * @set isScript
2464
+ ****
2465
+ * @method hasThai()
2466
+ * @set hasScript
2467
+ ***
2468
+ * @method isThai()
2469
+ * @set isScript
2470
+ ****
2471
+ * @method hasDevanagari()
2472
+ * @set hasScript
2473
+ ***
2474
+ * @method isDevanagari()
2475
+ * @set isScript
2476
+ ***/
2477
+ var unicodeScripts = [
2478
+ { names: ['Arabic'], source: '\u0600-\u06FF' },
2479
+ { names: ['Cyrillic'], source: '\u0400-\u04FF' },
2480
+ { names: ['Devanagari'], source: '\u0900-\u097F' },
2481
+ { names: ['Greek'], source: '\u0370-\u03FF' },
2482
+ { names: ['Hangul'], source: '\uAC00-\uD7AF\u1100-\u11FF' },
2483
+ { names: ['Han','Kanji'], source: '\u4E00-\u9FFF\uF900-\uFAFF' },
2484
+ { names: ['Hebrew'], source: '\u0590-\u05FF' },
2485
+ { names: ['Hiragana'], source: '\u3040-\u309F\u30FB-\u30FC' },
2486
+ { names: ['Kana'], source: '\u3040-\u30FF\uFF61-\uFF9F' },
2487
+ { names: ['Katakana'], source: '\u30A0-\u30FF\uFF61-\uFF9F' },
2488
+ { names: ['Latin'], source: '\u0001-\u007F\u0080-\u00FF\u0100-\u017F\u0180-\u024F' },
2489
+ { names: ['Thai'], source: '\u0E00-\u0E7F' }
2490
+ ];
2491
+
2492
+ function buildUnicodeScripts() {
2493
+ unicodeScripts.each(function(s) {
2494
+ var is = regexp('^['+s.source+'\\s]+$');
2495
+ var has = regexp('['+s.source+']');
2496
+ s.names.each(function(name) {
2497
+ defineProperty(string.prototype, 'is' + name, function() { return is.test(this.trim()); });
2498
+ defineProperty(string.prototype, 'has' + name, function() { return has.test(this); });
2499
+ });
2500
+ });
2501
+ }
2502
+
2503
+ function convertCharacterWidth(str, args, reg, table) {
2504
+ var mode = getArgs(args).join('');
2505
+ mode = mode.replace(/all/, '').replace(/(\w)lphabet|umbers?|atakana|paces?|unctuation/g, '$1');
2506
+ return str.replace(reg, function(c) {
2507
+ if(table[c] && (!mode || mode.has(table[c].type))) {
2508
+ return table[c].to;
2509
+ } else {
2510
+ return c;
2511
+ }
2512
+ });
2513
+ }
2514
+
2515
+ var widthConversionRanges = [
2516
+ { type: 'a', shift: 65248, start: 65, end: 90 },
2517
+ { type: 'a', shift: 65248, start: 97, end: 122 },
2518
+ { type: 'n', shift: 65248, start: 48, end: 57 },
2519
+ { type: 'p', shift: 65248, start: 33, end: 47 },
2520
+ { type: 'p', shift: 65248, start: 58, end: 64 },
2521
+ { type: 'p', shift: 65248, start: 91, end: 96 },
2522
+ { type: 'p', shift: 65248, start: 123, end: 126 }
2523
+ ];
2524
+
2525
+ var ZenkakuTable = {};
2526
+ var HankakuTable = {};
2527
+ var allHankaku = /[\u0020-\u00A5]|[\uFF61-\uFF9F][゙゚]?/g;
2528
+ var allZenkaku = /[\u3000-\u301C]|[\u301A-\u30FC]|[\uFF01-\uFF60]|[\uFFE0-\uFFE6]/g;
2529
+ var hankakuPunctuation = '。、「」¥¢£';
2530
+ var zenkakuPunctuation = '。、「」¥¢£';
2531
+ var voicedKatakana = /[カキクケコサシスセソタチツテトハヒフヘホ]/;
2532
+ var semiVoicedKatakana = /[ハヒフヘホヲ]/;
2533
+ var hankakuKatakana = 'アイウエオァィゥェォカキクケコサシスセソタチツッテトナニヌネノハヒフヘホマミムメモヤャユュヨョラリルレロワヲンー・';
2534
+ var zenkakuKatakana = 'アイウエオァィゥェォカキクケコサシスセソタチツッテトナニヌネノハヒフヘホマミムメモヤャユュヨョラリルレロワヲンー・';
2535
+
2536
+
2537
+ function buildWidthConversionTables() {
2538
+ var hankaku;
2539
+ arrayEach(widthConversionRanges, function(r) {
2540
+ r.start.upto(r.end, function(n) {
2541
+ setWidthConversion(r.type, n.chr(), (n + r.shift).chr());
2542
+ });
2543
+ });
2544
+ zenkakuKatakana.each(function(c, i) {
2545
+ hankaku = hankakuKatakana.charAt(i);
2546
+ setWidthConversion('k', hankaku, c);
2547
+ if(c.match(voicedKatakana)) {
2548
+ setWidthConversion('k', hankaku + '゙', c.shift(1));
2549
+ }
2550
+ if(c.match(semiVoicedKatakana)) {
2551
+ setWidthConversion('k', hankaku + '゚', c.shift(2));
2552
+ }
2553
+ });
2554
+ zenkakuPunctuation.each(function(c, i) {
2555
+ setWidthConversion('p', hankakuPunctuation.charAt(i), c);
2556
+ });
2557
+ setWidthConversion('k', 'ヴ', 'ヴ');
2558
+ setWidthConversion('k', 'ヺ', 'ヺ');
2559
+ setWidthConversion('s', ' ', ' ');
2560
+ }
2561
+
2562
+ function setWidthConversion(type, half, full) {
2563
+ ZenkakuTable[half] = { type: type, to: full };
2564
+ HankakuTable[full] = { type: type, to: half };
2565
+ }
2566
+
2567
+ function padString(str, p, left, right) {
2568
+ var padding = String(p);
2569
+ if(padding != p) {
2570
+ padding = '';
2571
+ }
2572
+ if(!object.isNumber(left)) left = 1;
2573
+ if(!object.isNumber(right)) right = 1;
2574
+ return padding.repeat(left) + str + padding.repeat(right);
2575
+ }
2576
+
2577
+ function getAcronym(word) {
2578
+ return string.Inflector && string.Inflector.acronyms && string.Inflector.acronyms[word];
2579
+ }
2580
+
2581
+ var btoa, atob;
2582
+
2583
+ function buildBase64(key) {
2584
+ if(this.btoa) {
2585
+ btoa = this.btoa;
2586
+ atob = this.atob;
2587
+ }
2588
+ var base64reg = /[^A-Za-z0-9\+\/\=]/g;
2589
+ btoa = function(str) {
2590
+ var output = '';
2591
+ var chr1, chr2, chr3;
2592
+ var enc1, enc2, enc3, enc4;
2593
+ var i = 0;
2594
+ do {
2595
+ chr1 = str.charCodeAt(i++);
2596
+ chr2 = str.charCodeAt(i++);
2597
+ chr3 = str.charCodeAt(i++);
2598
+ enc1 = chr1 >> 2;
2599
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
2600
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
2601
+ enc4 = chr3 & 63;
2602
+ if (isNaN(chr2)) {
2603
+ enc3 = enc4 = 64;
2604
+ } else if (isNaN(chr3)) {
2605
+ enc4 = 64;
2606
+ }
2607
+ output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4);
2608
+ chr1 = chr2 = chr3 = '';
2609
+ enc1 = enc2 = enc3 = enc4 = '';
2610
+ } while (i < str.length);
2611
+ return output;
2612
+ }
2613
+ atob = function(input) {
2614
+ var output = '';
2615
+ var chr1, chr2, chr3;
2616
+ var enc1, enc2, enc3, enc4;
2617
+ var i = 0;
2618
+ if(input.match(base64reg)) {
2619
+ throw new Error('String contains invalid base64 characters');
2620
+ }
2621
+ input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
2622
+ do {
2623
+ enc1 = key.indexOf(input.charAt(i++));
2624
+ enc2 = key.indexOf(input.charAt(i++));
2625
+ enc3 = key.indexOf(input.charAt(i++));
2626
+ enc4 = key.indexOf(input.charAt(i++));
2627
+ chr1 = (enc1 << 2) | (enc2 >> 4);
2628
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
2629
+ chr3 = ((enc3 & 3) << 6) | enc4;
2630
+ output = output + chr1.chr();
2631
+ if (enc3 != 64) {
2632
+ output = output + chr2.chr();
2633
+ }
2634
+ if (enc4 != 64) {
2635
+ output = output + chr3.chr();
2636
+ }
2637
+ chr1 = chr2 = chr3 = '';
2638
+ enc1 = enc2 = enc3 = enc4 = '';
2639
+ } while (i < input.length);
2640
+ return unescape(output);
2641
+ }
2642
+ }
2643
+
2644
+ function buildTrim() {
2645
+ var support = getTrimmableCharacters().match(/^\s+$/);
2646
+ try { string.prototype.trim.call([1]); } catch(e) { support = false; }
2647
+ var trimL = regexp('^['+getTrimmableCharacters()+']+');
2648
+ var trimR = regexp('['+getTrimmableCharacters()+']+$');
2649
+ extend(string, true, !support, {
2650
+
2651
+ /***
2652
+ * @method trim[Side]()
2653
+ * @returns String
2654
+ * @short Removes leading and/or trailing whitespace from the string.
2655
+ * @extra Whitespace is defined as line breaks, tabs, and any character in the "Space, Separator" Unicode category, conforming to the the ES5 spec. The standard %trim% method is only added when not fully supported natively.
2656
+ * @example
2657
+ *
2658
+ * ' wasabi '.trim() -> 'wasabi'
2659
+ * ' wasabi '.trimLeft() -> 'wasabi '
2660
+ * ' wasabi '.trimRight() -> ' wasabi'
2661
+ *
2662
+ ***
2663
+ * @method trim()
2664
+ * @set trimSide
2665
+ ***/
2666
+ 'trim': function() {
2667
+ return this.toString().trimLeft().trimRight();
2668
+ },
2669
+
2670
+ /***
2671
+ * @method trimLeft()
2672
+ * @set trimSide
2673
+ ***/
2674
+ 'trimLeft': function() {
2675
+ return this.replace(trimL, '');
2676
+ },
2677
+
2678
+ /***
2679
+ * @method trimRight()
2680
+ * @set trimSide
2681
+ ***/
2682
+ 'trimRight': function() {
2683
+ return this.replace(trimR, '');
2684
+ }
2685
+ });
2686
+ }
2687
+
2688
+ function buildString() {
2689
+ buildBase64('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=');
2690
+ buildTrim();
2691
+ buildWidthConversionTables();
2692
+ buildUnicodeScripts();
2693
+ }
2694
+
2695
+
2696
+
2697
+ extend(string, true, false, {
2698
+
2699
+ /***
2700
+ * @method escapeRegExp()
2701
+ * @returns String
2702
+ * @short Escapes all RegExp tokens in the string.
2703
+ * @example
2704
+ *
2705
+ * 'really?'.escapeRegExp() -> 'really\?'
2706
+ * 'yes.'.escapeRegExp() -> 'yes\.'
2707
+ * '(not really)'.escapeRegExp() -> '\(not really\)'
2708
+ *
2709
+ ***/
2710
+ 'escapeRegExp': function() {
2711
+ return regexp.escape(this);
2712
+ },
2713
+
2714
+ /***
2715
+ * @method escapeURL([param] = false)
2716
+ * @returns String
2717
+ * @short Escapes characters in a string to make a valid URL.
2718
+ * @extra If [param] is true, it will also escape valid URL characters for use as a URL parameter.
2719
+ * @example
2720
+ *
2721
+ * 'http://foo.com/"bar"'.escapeURL() -> 'http://foo.com/%22bar%22'
2722
+ * 'http://foo.com/"bar"'.escapeURL(true) -> 'http%3A%2F%2Ffoo.com%2F%22bar%22'
2723
+ *
2724
+ ***/
2725
+ 'escapeURL': function(param) {
2726
+ return param ? encodeURIComponent(this) : encodeURI(this);
2727
+ },
2728
+
2729
+ /***
2730
+ * @method unescapeURL([partial] = false)
2731
+ * @returns String
2732
+ * @short Restores escaped characters in a URL escaped string.
2733
+ * @extra If [partial] is true, it will only unescape non-valid URL characters. [partial] is included here for completeness, but should very rarely be needed.
2734
+ * @example
2735
+ *
2736
+ * 'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL() -> 'http://foo.com/the bar'
2737
+ * 'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL(true) -> 'http%3A%2F%2Ffoo.com%2Fthe bar'
2738
+ *
2739
+ ***/
2740
+ 'unescapeURL': function(param) {
2741
+ return param ? decodeURI(this) : decodeURIComponent(this);
2742
+ },
2743
+
2744
+ /***
2745
+ * @method escapeHTML()
2746
+ * @returns String
2747
+ * @short Converts HTML characters to their entity equivalents.
2748
+ * @example
2749
+ *
2750
+ * '<p>some text</p>'.escapeHTML() -> '&lt;p&gt;some text&lt;/p&gt;'
2751
+ * 'one & two'.escapeHTML() -> 'one &amp; two'
2752
+ *
2753
+ ***/
2754
+ 'escapeHTML': function() {
2755
+ return this.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
2756
+ },
2757
+
2758
+ /***
2759
+ * @method unescapeHTML([partial] = false)
2760
+ * @returns String
2761
+ * @short Restores escaped HTML characters.
2762
+ * @example
2763
+ *
2764
+ * '&lt;p&gt;some text&lt;/p&gt;'.unescapeHTML() -> '<p>some text</p>'
2765
+ * 'one &amp; two'.unescapeHTML() -> 'one & two'
2766
+ *
2767
+ ***/
2768
+ 'unescapeHTML': function() {
2769
+ return this.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
2770
+ },
2771
+
2772
+ /***
2773
+ * @method encodeBase64()
2774
+ * @returns String
2775
+ * @short Encodes the string into base 64 encoding.
2776
+ * @extra This methods wraps the browser native %btoa% when available, and uses a custom implementation when not available.
2777
+ * @example
2778
+ *
2779
+ * 'gonna get encoded!'.encodeBase64() -> 'Z29ubmEgZ2V0IGVuY29kZWQh'
2780
+ * 'http://twitter.com/'.encodeBase64() -> 'aHR0cDovL3R3aXR0ZXIuY29tLw=='
2781
+ *
2782
+ ***/
2783
+ 'encodeBase64': function() {
2784
+ return btoa(this);
2785
+ },
2786
+
2787
+ /***
2788
+ * @method decodeBase64()
2789
+ * @returns String
2790
+ * @short Decodes the string from base 64 encoding.
2791
+ * @extra This methods wraps the browser native %atob% when available, and uses a custom implementation when not available.
2792
+ * @example
2793
+ *
2794
+ * 'aHR0cDovL3R3aXR0ZXIuY29tLw=='.decodeBase64() -> 'http://twitter.com/'
2795
+ * 'anVzdCBnb3QgZGVjb2RlZA=='.decodeBase64() -> 'just got decoded!'
2796
+ *
2797
+ ***/
2798
+ 'decodeBase64': function() {
2799
+ return atob(this);
2800
+ },
2801
+
2802
+ /***
2803
+ * @method capitalize([all] = false)
2804
+ * @returns String
2805
+ * @short Capitalizes the first character in the string.
2806
+ * @extra If [all] is true, all words in the string will be capitalized.
2807
+ * @example
2808
+ *
2809
+ * 'hello'.capitalize() -> 'hello'
2810
+ * 'hello kitty'.capitalize() -> 'hello kitty'
2811
+ * 'hello kitty'.capitalize(true) -> 'hello kitty'
2812
+ *
2813
+ *
2814
+ ***/
2815
+ 'capitalize': function(all) {
2816
+ var reg = all ? /^\S|\s\S/g : /^\S/;
2817
+ return this.toLowerCase().replace(reg, function(letter) {
2818
+ return letter.toUpperCase();
2819
+ });
2820
+ },
2821
+
2822
+ /***
2823
+ * @method pad[Side](<padding> = '', [num] = 1)
2824
+ * @returns String
2825
+ * @short Pads either/both sides of the string.
2826
+ * @extra [num] is the number of characters on each side, and [padding] is the character to pad with.
2827
+ * @example
2828
+ *
2829
+ * 'wasabi'.pad('-') -> '-wasabi-'
2830
+ * 'wasabi'.pad('-', 2) -> '--wasabi--'
2831
+ * 'wasabi'.padLeft('-', 2) -> '--wasabi'
2832
+ * 'wasabi'.padRight('-', 2) -> 'wasabi--'
2833
+ *
2834
+ ***
2835
+ * @method pad()
2836
+ * @set padSide
2837
+ ***/
2838
+ 'pad': function(padding, num) {
2839
+ return padString(this, padding, num, num);
2840
+ },
2841
+
2842
+ /***
2843
+ * @method padLeft()
2844
+ * @set padSide
2845
+ ***/
2846
+ 'padLeft': function(padding, num) {
2847
+ return padString(this, padding, num, 0);
2848
+ },
2849
+
2850
+ /***
2851
+ * @method padRight()
2852
+ * @set padSide
2853
+ ***/
2854
+ 'padRight': function(padding, num) {
2855
+ return padString(this, padding, 0, num);
2856
+ },
2857
+
2858
+ /***
2859
+ * @method repeat([num] = 0)
2860
+ * @returns String
2861
+ * @short Returns the string repeated [num] times.
2862
+ * @example
2863
+ *
2864
+ * 'jumpy'.repeat(2) -> 'jumpyjumpy'
2865
+ * 'a'.repeat(5) -> 'aaaaa'
2866
+ *
2867
+ ***/
2868
+ 'repeat': function(num) {
2869
+ var str = '', i = 0;
2870
+ if(object.isNumber(num) && num > 0) {
2871
+ while(i < num) {
2872
+ str += this;
2873
+ i++;
2874
+ }
2875
+ }
2876
+ return str;
2877
+ },
2878
+
2879
+ /***
2880
+ * @method each([search] = single character, [fn])
2881
+ * @returns Array
2882
+ * @short Runs callback [fn] against each occurence of [search].
2883
+ * @extra Returns an array of matches. [search] may be either a string or regex, and defaults to every character in the string.
2884
+ * @example
2885
+ *
2886
+ * 'jumpy'.each() -> ['j','u','m','p','y']
2887
+ * 'jumpy'.each(/[r-z]/) -> ['u','y']
2888
+ * 'jumpy'.each(/[r-z]/, function(m) {
2889
+ * // Called twice: "u", "y"
2890
+ * });
2891
+ *
2892
+ ***/
2893
+ 'each': function(search, fn) {
2894
+ if(object.isFunction(search)) {
2895
+ fn = search;
2896
+ search = /[\s\S]/g;
2897
+ } else if(!search) {
2898
+ search = /[\s\S]/g
2899
+ } else if(object.isString(search)) {
2900
+ search = regexp(regexp.escape(search), 'gi');
2901
+ } else if(object.isRegExp(search)) {
2902
+ search = search.addFlag('g');
2903
+ }
2904
+ var match = this.match(search) || [];
2905
+ if(fn) {
2906
+ for(var i = 0; i < match.length; i++) {
2907
+ match[i] = fn.call(this, match[i], i, match) || match[i];
2908
+ }
2909
+ }
2910
+ return match;
2911
+ },
2912
+
2913
+ /***
2914
+ * @method shift(<n>)
2915
+ * @returns Array
2916
+ * @short Shifts each character in the string <n> places in the character map.
2917
+ * @example
2918
+ *
2919
+ * 'a'.shift(1) -> 'b'
2920
+ * 'ク'.shift(1) -> 'グ'
2921
+ *
2922
+ ***/
2923
+ 'shift': function(n) {
2924
+ var result = '';
2925
+ n = n || 0;
2926
+ this.codes(function(c) {
2927
+ result += (c + n).chr();
2928
+ });
2929
+ return result;
2930
+ },
2931
+
2932
+ /***
2933
+ * @method codes([fn])
2934
+ * @returns Array
2935
+ * @short Runs callback [fn] against each character code in the string. Returns an array of character codes.
2936
+ * @example
2937
+ *
2938
+ * 'jumpy'.codes() -> [106,117,109,112,121]
2939
+ * 'jumpy'.codes(function(c) {
2940
+ * // Called 5 times: 106, 117, 109, 112, 121
2941
+ * });
2942
+ *
2943
+ ***/
2944
+ 'codes': function(fn) {
2945
+ var codes = [];
2946
+ for(var i=0; i<this.length; i++) {
2947
+ var code = this.charCodeAt(i);
2948
+ codes.push(code);
2949
+ if(fn) fn.call(this, code, i);
2950
+ }
2951
+ return codes;
2952
+ },
2953
+
2954
+ /***
2955
+ * @method chars([fn])
2956
+ * @returns Array
2957
+ * @short Runs callback [fn] against each character in the string. Returns an array of characters.
2958
+ * @example
2959
+ *
2960
+ * 'jumpy'.chars() -> ['j','u','m','p','y']
2961
+ * 'jumpy'.chars(function(c) {
2962
+ * // Called 5 times: "j","u","m","p","y"
2963
+ * });
2964
+ *
2965
+ ***/
2966
+ 'chars': function(fn) {
2967
+ return this.each(fn);
2968
+ },
2969
+
2970
+ /***
2971
+ * @method words([fn])
2972
+ * @returns Array
2973
+ * @short Runs callback [fn] against each word in the string. Returns an array of words.
2974
+ * @extra A "word" here is defined as any sequence of non-whitespace characters.
2975
+ * @example
2976
+ *
2977
+ * 'broken wear'.words() -> ['broken','wear']
2978
+ * 'broken wear'.words(function(w) {
2979
+ * // Called twice: "broken", "wear"
2980
+ * });
2981
+ *
2982
+ ***/
2983
+ 'words': function(fn) {
2984
+ return this.trim().each(/\S+/g, fn);
2985
+ },
2986
+
2987
+ /***
2988
+ * @method lines([fn])
2989
+ * @returns Array
2990
+ * @short Runs callback [fn] against each line in the string. Returns an array of lines.
2991
+ * @example
2992
+ *
2993
+ * 'broken wear\nand\njumpy jump'.lines() -> ['broken wear','and','jumpy jump']
2994
+ * 'broken wear\nand\njumpy jump'.lines(function(l) {
2995
+ * // Called three times: "broken wear", "and", "jumpy jump"
2996
+ * });
2997
+ *
2998
+ ***/
2999
+ 'lines': function(fn) {
3000
+ return this.trim().each(/^.*$/gm, fn);
3001
+ },
3002
+
3003
+ /***
3004
+ * @method paragraphs([fn])
3005
+ * @returns Array
3006
+ * @short Runs callback [fn] against each paragraph in the string. Returns an array of paragraphs.
3007
+ * @extra A paragraph here is defined as a block of text bounded by two or more line breaks.
3008
+ * @example
3009
+ *
3010
+ * 'Once upon a time.\n\nIn the land of oz...'.paragraphs() -> ['Once upon a time.','In the land of oz...']
3011
+ * 'Once upon a time.\n\nIn the land of oz...'.paragraphs(function(p) {
3012
+ * // Called twice: "Once upon a time.", "In teh land of oz..."
3013
+ * });
3014
+ *
3015
+ ***/
3016
+ 'paragraphs': function(fn) {
3017
+ var paragraphs = this.trim().split(/[\r\n]{2,}/);
3018
+ paragraphs = paragraphs.map(function(p) {
3019
+ if(fn) var s = fn.call(p);
3020
+ return s ? s : p;
3021
+ });
3022
+ return paragraphs;
3023
+ },
3024
+
3025
+ /***
3026
+ * @method startsWith(<find>, [case] = true)
3027
+ * @returns Boolean
3028
+ * @short Returns true if the string starts with <find>.
3029
+ * @extra <find> may be either a string or regex. Case sensitive if [case] is true.
3030
+ * @example
3031
+ *
3032
+ * 'hello'.startsWith('hell') -> true
3033
+ * 'hello'.startsWith(/[a-h]/) -> true
3034
+ * 'hello'.startsWith('HELL') -> false
3035
+ * 'hello'.startsWith('HELL', false) -> true
3036
+ *
3037
+ ***/
3038
+ 'startsWith': function(reg, c) {
3039
+ if(isUndefined(c)) c = true;
3040
+ var source = object.isRegExp(reg) ? reg.source.replace('^', '') : regexp.escape(reg);
3041
+ return regexp('^' + source, c ? '' : 'i').test(this);
3042
+ },
3043
+
3044
+ /***
3045
+ * @method endsWith(<find>, [case] = true)
3046
+ * @returns Boolean
3047
+ * @short Returns true if the string ends with <find>.
3048
+ * @extra <find> may be either a string or regex. Case sensitive if [case] is true.
3049
+ * @example
3050
+ *
3051
+ * 'jumpy'.endsWith('py') -> true
3052
+ * 'jumpy'.endsWith(/[q-z]/) -> true
3053
+ * 'jumpy'.endsWith('MPY') -> false
3054
+ * 'jumpy'.endsWith('MPY', false) -> true
3055
+ *
3056
+ ***/
3057
+ 'endsWith': function(reg, c) {
3058
+ if(isUndefined(c)) c = true;
3059
+ var source = object.isRegExp(reg) ? reg.source.replace('$', '') : regexp.escape(reg);
3060
+ return regexp(source + '$', c ? '' : 'i').test(this);
3061
+ },
3062
+
3063
+ /***
3064
+ * @method isBlank()
3065
+ * @returns Boolean
3066
+ * @short Returns true if the string has a length of 0 or contains only whitespace.
3067
+ * @example
3068
+ *
3069
+ * ''.isBlank() -> true
3070
+ * ' '.isBlank() -> true
3071
+ * 'noway'.isBlank() -> false
3072
+ *
3073
+ ***/
3074
+ 'isBlank': function() {
3075
+ return this.trim().length === 0;
3076
+ },
3077
+
3078
+ /***
3079
+ * @method has(<find>)
3080
+ * @returns Boolean
3081
+ * @short Returns true if the string matches <find>.
3082
+ * @extra <find> may be a string or regex.
3083
+ * @example
3084
+ *
3085
+ * 'jumpy'.has('py') -> true
3086
+ * 'broken'.has(/[a-n]/) -> true
3087
+ * 'broken'.has(/[s-z]/) -> false
3088
+ *
3089
+ ***/
3090
+ 'has': function(find) {
3091
+ return this.search(object.isRegExp(find) ? find : RegExp.escape(find)) !== -1;
3092
+ },
3093
+
3094
+
3095
+ /***
3096
+ * @method add(<str>, [index] = 0)
3097
+ * @returns String
3098
+ * @short Adds <str> at [index]. Negative values are also allowed.
3099
+ * @extra %insert% is provided as an alias, and is generally more readable when using an index.
3100
+ * @example
3101
+ *
3102
+ * 'schfifty'.add(' five') -> schfifty five
3103
+ * 'dopamine'.insert('e', 3) -> dopeamine
3104
+ * 'spelling eror'.insert('r', -3) -> spelling error
3105
+ *
3106
+ ***/
3107
+ 'add': function(str, index) {
3108
+ return this.split('').add(str, index).join('');
3109
+ },
3110
+
3111
+ /***
3112
+ * @method remove(<f>)
3113
+ * @returns String
3114
+ * @short Removes any part of the string that matches <f>.
3115
+ * @extra <f> can be a string or a regex.
3116
+ * @example
3117
+ *
3118
+ * 'schfifty five'.remove('f') -> 'schity ive'
3119
+ * 'schfifty five'.remove(/[a-f]/g) -> 'shity iv'
3120
+ *
3121
+ ***/
3122
+ 'remove': function(f) {
3123
+ return this.replace(f, '');
3124
+ },
3125
+
3126
+ /***
3127
+ * @method hankaku([mode] = 'all')
3128
+ * @returns String
3129
+ * @short Converts full-width characters (zenkaku) to half-width (hankaku).
3130
+ * @extra [mode] accepts any combination of "a" (alphabet), "n" (numbers), "k" (katakana), "s" (spaces), "p" (punctuation), or "all".
3131
+ * @example
3132
+ *
3133
+ * 'タロウ YAMADAです!'.hankaku() -> 'タロウ YAMADAです!'
3134
+ * 'タロウ YAMADAです!'.hankaku('a') -> 'タロウ YAMADAです!'
3135
+ * 'タロウ YAMADAです!'.hankaku('alphabet') -> 'タロウ YAMADAです!'
3136
+ * 'タロウです! 25歳です!'.hankaku('katakana', 'numbers') -> 'タロウです! 25歳です!'
3137
+ * 'タロウです! 25歳です!'.hankaku('k', 'n') -> 'タロウです! 25歳です!'
3138
+ * 'タロウです! 25歳です!'.hankaku('kn') -> 'タロウです! 25歳です!'
3139
+ * 'タロウです! 25歳です!'.hankaku('sp') -> 'タロウです! 25歳です!'
3140
+ *
3141
+ ***/
3142
+ 'hankaku': function() {
3143
+ return convertCharacterWidth(this, arguments, allZenkaku, HankakuTable);
3144
+ },
3145
+
3146
+ /***
3147
+ * @method zenkaku([mode] = 'all')
3148
+ * @returns String
3149
+ * @short Converts half-width characters (hankaku) to full-width (zenkaku).
3150
+ * @extra [mode] accepts any combination of "a" (alphabet), "n" (numbers), "k" (katakana), "s" (spaces), "p" (punctuation), or "all".
3151
+ * @example
3152
+ *
3153
+ * 'タロウ YAMADAです!'.zenkaku() -> 'タロウ YAMADAです!'
3154
+ * 'タロウ YAMADAです!'.zenkaku('a') -> 'タロウ YAMADAです!'
3155
+ * 'タロウ YAMADAです!'.zenkaku('alphabet') -> 'タロウ YAMADAです!'
3156
+ * 'タロウです! 25歳です!'.zenkaku('katakana', 'numbers') -> 'タロウです! 25歳です!'
3157
+ * 'タロウです! 25歳です!'.zenkaku('k', 'n') -> 'タロウです! 25歳です!'
3158
+ * 'タロウです! 25歳です!'.zenkaku('kn') -> 'タロウです! 25歳です!'
3159
+ * 'タロウです! 25歳です!'.zenkaku('sp') -> 'タロウです! 25歳です!'
3160
+ *
3161
+ ***/
3162
+ 'zenkaku': function() {
3163
+ return convertCharacterWidth(this, arguments, allHankaku, ZenkakuTable);
3164
+ },
3165
+
3166
+ /***
3167
+ * @method hiragana([all] = true)
3168
+ * @returns String
3169
+ * @short Converts katakana into hiragana.
3170
+ * @extra If [all] is false, only full-width katakana will be converted.
3171
+ * @example
3172
+ *
3173
+ * 'カタカナ'.hiragana() -> 'かたかな'
3174
+ * 'コンニチハ'.hiragana() -> 'こんにちは'
3175
+ * 'カタカナ'.hiragana() -> 'かたかな'
3176
+ * 'カタカナ'.hiragana(false) -> 'カタカナ'
3177
+ *
3178
+ ***/
3179
+ 'hiragana': function(all) {
3180
+ var str = this;
3181
+ if(all !== false) {
3182
+ str = str.zenkaku('k');
3183
+ }
3184
+ return str.replace(/[\u30A1-\u30F6]/g, function(c) {
3185
+ return c.shift(-96);
3186
+ });
3187
+ },
3188
+
3189
+ /***
3190
+ * @method katakana()
3191
+ * @returns String
3192
+ * @short Converts hiragana into katakana.
3193
+ * @example
3194
+ *
3195
+ * 'かたかな'.katakana() -> 'カタカナ'
3196
+ * 'こんにちは'.katakana() -> 'コンニチハ'
3197
+ *
3198
+ ***/
3199
+ 'katakana': function() {
3200
+ return this.replace(/[\u3041-\u3096]/g, function(c) {
3201
+ return c.shift(96);
3202
+ });
3203
+ },
3204
+
3205
+ /***
3206
+ * @method toNumber([base] = 10)
3207
+ * @returns Number
3208
+ * @short Converts the string into a number.
3209
+ * @extra Any value with a "." fill be converted to a floating point value, otherwise an integer.
3210
+ * @example
3211
+ *
3212
+ * '153'.toNumber() -> 153
3213
+ * '12,000'.toNumber() -> 12000
3214
+ * '10px'.toNumber() -> 10
3215
+ * 'ff'.toNumber(16) -> 255
3216
+ *
3217
+ ***/
3218
+ 'toNumber': function(base) {
3219
+ var str = this.replace(/,/g, '');
3220
+ return str.match(/\./) ? parseFloat(str) : parseInt(str, base || 10);
3221
+ },
3222
+
3223
+ /***
3224
+ * @method reverse()
3225
+ * @returns String
3226
+ * @short Reverses the string.
3227
+ * @example
3228
+ *
3229
+ * 'jumpy'.reverse() -> 'ypmuj'
3230
+ * 'lucky charms'.reverse() -> 'smrahc ykcul'
3231
+ *
3232
+ ***/
3233
+ 'reverse': function() {
3234
+ return this.split('').reverse().join('');
3235
+ },
3236
+
3237
+ /***
3238
+ * @method compact()
3239
+ * @returns String
3240
+ * @short Compacts all white space in the string to a single space and trims the ends.
3241
+ * @example
3242
+ *
3243
+ * 'too \n much \n space'.compact() -> 'too much space'
3244
+ * 'enough \n '.compact() -> 'enought'
3245
+ *
3246
+ ***/
3247
+ 'compact': function() {
3248
+ return this.trim().replace(/([\r\n\s ])+/g, function(match, whitespace){
3249
+ return whitespace === ' ' ? whitespace : ' ';
3250
+ });
3251
+ },
3252
+
3253
+ /***
3254
+ * @method at(<index>, [loop] = true)
3255
+ * @returns String or Array
3256
+ * @short Gets the character(s) at a given index.
3257
+ * @extra When [loop] is true, overshooting the end of the string (or the beginning) will begin counting from the other end. As an alternate syntax, passing multiple indexes will get the characters at those indexes.
3258
+ * @example
3259
+ *
3260
+ * 'jumpy'.at(0) -> 'j'
3261
+ * 'jumpy'.at(2) -> 'm'
3262
+ * 'jumpy'.at(5) -> 'j'
3263
+ * 'jumpy'.at(5, false) -> ''
3264
+ * 'jumpy'.at(-1) -> 'y'
3265
+ * 'luckly charms'.at(1,3,5,7) -> ['u','k','y',c']
3266
+ *
3267
+ ***/
3268
+ 'at': function() {
3269
+ return entryAtIndex(this, arguments, true);
3270
+ },
3271
+
3272
+ /***
3273
+ * @method first([n] = 1)
3274
+ * @returns String
3275
+ * @short Returns the first [n] characters of the string.
3276
+ * @example
3277
+ *
3278
+ * 'lucky charms'.first() -> 'l'
3279
+ * 'lucky charms'.first(3) -> 'luc'
3280
+ *
3281
+ ***/
3282
+ 'first': function(num) {
3283
+ if(isUndefined(num)) num = 1;
3284
+ return this.substr(0, num);
3285
+ },
3286
+
3287
+ /***
3288
+ * @method last([n] = 1)
3289
+ * @returns String
3290
+ * @short Returns the last [n] characters of the string.
3291
+ * @example
3292
+ *
3293
+ * 'lucky charms'.last() -> 's'
3294
+ * 'lucky charms'.last(3) -> 'rms'
3295
+ *
3296
+ ***/
3297
+ 'last': function(num) {
3298
+ if(isUndefined(num)) num = 1;
3299
+ var start = this.length - num < 0 ? 0 : this.length - num;
3300
+ return this.substr(start);
3301
+ },
3302
+
3303
+ /***
3304
+ * @method from([index] = 0)
3305
+ * @returns String
3306
+ * @short Returns a section of the string starting from [index].
3307
+ * @example
3308
+ *
3309
+ * 'lucky charms'.from() -> 'lucky charms'
3310
+ * 'lucky charms'.from(7) -> 'harms'
3311
+ *
3312
+ ***/
3313
+ 'from': function(num) {
3314
+ return this.slice(num);
3315
+ },
3316
+
3317
+ /***
3318
+ * @method to([index] = end)
3319
+ * @returns String
3320
+ * @short Returns a section of the string ending at [index].
3321
+ * @example
3322
+ *
3323
+ * 'lucky charms'.to() -> 'lucky charms'
3324
+ * 'lucky charms'.to(7) -> 'lucky ch'
3325
+ *
3326
+ ***/
3327
+ 'to': function(num) {
3328
+ if(isUndefined(num)) num = this.length;
3329
+ return this.slice(0, num);
3330
+ },
3331
+
3332
+ /***
3333
+ * @method toDate([locale])
3334
+ * @returns Date
3335
+ * @short Creates a date from the string.
3336
+ * @extra Accepts a wide range of input. [locale] allows you to specify a locale code. See @date_format for more information.
3337
+ * @example
3338
+ *
3339
+ * 'January 25, 2015'.toDate() -> same as Date.create('January 25, 2015')
3340
+ * 'yesterday'.toDate() -> same as Date.create('yesterday')
3341
+ * 'next Monday'.toDate() -> same as Date.create('next Monday')
3342
+ *
3343
+ ***/
3344
+ 'toDate': function(locale) {
3345
+ var str = this.toString();
3346
+ return date.create ? date.create(str, locale) : new date(str);
3347
+ },
3348
+
3349
+ /***
3350
+ * @method dasherize()
3351
+ * @returns String
3352
+ * @short Converts underscores and camel casing to hypens.
3353
+ * @example
3354
+ *
3355
+ * 'a_farewell_to_arms'.dasherize() -> 'a-farewell-to-arms'
3356
+ * 'capsLock'.dasherize() -> 'caps-lock'
3357
+ *
3358
+ ***/
3359
+ 'dasherize': function() {
3360
+ return this.underscore().replace(/_/g, '-');
3361
+ },
3362
+
3363
+ /***
3364
+ * @method underscore()
3365
+ * @returns String
3366
+ * @short Converts hyphens and camel casing to underscores.
3367
+ * @example
3368
+ *
3369
+ * 'a-farewell-to-arms'.underscore() -> 'a_farewell_to_arms'
3370
+ * 'capsLock'.underscore() -> 'caps_lock'
3371
+ *
3372
+ ***/
3373
+ 'underscore': function() {
3374
+ return this
3375
+ .replace(/[-\s]+/g, '_')
3376
+ .replace(String.Inflector && String.Inflector.acronymRegExp, function(acronym, index) {
3377
+ return (index > 0 ? '_' : '') + acronym.toLowerCase();
3378
+ })
3379
+ .replace(/([A-Z\d]+)([A-Z][a-z])/g,'$1_$2')
3380
+ .replace(/([a-z\d])([A-Z])/g,'$1_$2')
3381
+ .toLowerCase();
3382
+ },
3383
+
3384
+ /***
3385
+ * @method camelize([first] = true)
3386
+ * @returns String
3387
+ * @short Converts underscores and hyphens to camel case. If [first] is true the first letter will also be capitalized.
3388
+ * @example
3389
+ *
3390
+ * 'caps_lock'.camelize() -> 'CapsLock'
3391
+ * 'moz-border-radius'.camelize() -> 'MozBorderRadius'
3392
+ * 'moz-border-radius'.camelize(false) -> 'mozBorderRadius'
3393
+ *
3394
+ ***/
3395
+ 'camelize': function(first) {
3396
+ return this.underscore().replace(/(^|_)([^_]+)/g, function(match, pre, word, index) {
3397
+ var acronym = getAcronym(word), capitalize = first !== false || index > 0;
3398
+ if(acronym) return capitalize ? acronym : acronym.toLowerCase();
3399
+ return capitalize ? word.capitalize() : word;
3400
+ });
3401
+ },
3402
+
3403
+ /***
3404
+ * @method spacify()
3405
+ * @returns String
3406
+ * @short Converts camel case, underscores, and hyphens to a properly spaced string.
3407
+ * @example
3408
+ *
3409
+ * 'camelCase'.spacify() -> 'camel case'
3410
+ * 'an-ugly-string'.spacify() -> 'an ugly string'
3411
+ * 'oh-no_youDid-not'.spacify().capitalize(true) -> 'something else'
3412
+ *
3413
+ ***/
3414
+ 'spacify': function() {
3415
+ return this.underscore().replace(/_/g, ' ');
3416
+ },
3417
+
3418
+ /***
3419
+ * @method stripTags([tag1], [tag2], ...)
3420
+ * @returns String
3421
+ * @short Strips all HTML tags from the string.
3422
+ * @extra Tags to strip may be enumerated in the parameters, otherwise will strip all.
3423
+ * @example
3424
+ *
3425
+ * '<p>just <b>some</b> text</p>'.stripTags() -> 'just some text'
3426
+ * '<p>just <b>some</b> text</p>'.stripTags('p') -> 'just <b>some</b> text'
3427
+ *
3428
+ ***/
3429
+ 'stripTags': function() {
3430
+ var str = this, args = arguments.length > 0 ? arguments : [''];
3431
+ multiArgs(args, function(tag) {
3432
+ str = str.replace(regexp('<\/?' + tag.escapeRegExp() + '[^<>]*>', 'gi'), '');
3433
+ });
3434
+ return str;
3435
+ },
3436
+
3437
+ /***
3438
+ * @method removeTags([tag1], [tag2], ...)
3439
+ * @returns String
3440
+ * @short Removes all HTML tags and their contents from the string.
3441
+ * @extra Tags to remove may be enumerated in the parameters, otherwise will remove all.
3442
+ * @example
3443
+ *
3444
+ * '<p>just <b>some</b> text</p>'.removeTags() -> ''
3445
+ * '<p>just <b>some</b> text</p>'.removeTags('b') -> '<p>just text</p>'
3446
+ *
3447
+ ***/
3448
+ 'removeTags': function() {
3449
+ var str = this, args = arguments.length > 0 ? arguments : ['\\S+'];
3450
+ multiArgs(args, function(t) {
3451
+ var reg = regexp('<(' + t + ')[^<>]*(?:\\/>|>.*?<\\/\\1>)', 'gi');
3452
+ str = str.replace(reg, '');
3453
+ });
3454
+ return str;
3455
+ },
3456
+
3457
+ /***
3458
+ * @method truncate(<length>, [split] = true, from = 'right', [ellipsis] = '...')
3459
+ * @returns Object
3460
+ * @short Truncates a string.
3461
+ * @extra If [split] is %false%, will not split words up, and instead discard the word where the truncation occurred. [from] can also be %"middle"% or %"left"%.
3462
+ * @example
3463
+ *
3464
+ * 'just sittin on the dock of the bay'.truncate(20) -> 'just sittin on the do...'
3465
+ * 'just sittin on the dock of the bay'.truncate(20, false) -> 'just sittin on the...'
3466
+ * 'just sittin on the dock of the bay'.truncate(20, true, 'middle') -> 'just sitt...of the bay'
3467
+ * 'just sittin on the dock of the bay'.truncate(20, true, 'middle') -> '...the dock of the bay'
3468
+ *
3469
+ ***/
3470
+ 'truncate': function(length, split, from, ellipsis) {
3471
+ var pos,
3472
+ prepend = '',
3473
+ append = '',
3474
+ str = this.toString(),
3475
+ chars = '[' + getTrimmableCharacters() + ']+',
3476
+ space = '[^' + getTrimmableCharacters() + ']*',
3477
+ reg = regexp(chars + space + '$');
3478
+ ellipsis = isUndefined(ellipsis) ? '...' : string(ellipsis);
3479
+ if(str.length <= length) {
3480
+ return str;
3481
+ }
3482
+ switch(from) {
3483
+ case 'left':
3484
+ pos = str.length - length;
3485
+ prepend = ellipsis;
3486
+ str = str.slice(pos);
3487
+ reg = regexp('^' + space + chars);
3488
+ break;
3489
+ case 'middle':
3490
+ pos = Math.floor(length / 2);
3491
+ append = ellipsis + str.slice(str.length - pos).trimLeft();
3492
+ str = str.slice(0, pos);
3493
+ break;
3494
+ default:
3495
+ pos = length;
3496
+ append = ellipsis;
3497
+ str = str.slice(0, pos);
3498
+ }
3499
+ if(split === false && this.slice(pos, pos + 1).match(/\S/)) {
3500
+ str = str.remove(reg);
3501
+ }
3502
+ return prepend + str + append;
3503
+ },
3504
+
3505
+ /***
3506
+ * @method assign(<obj1>, <obj2>, ...)
3507
+ * @returns String
3508
+ * @short Assigns variables to tokens in a string.
3509
+ * @extra If an object is passed, it's properties can be assigned using the object's keys. If a non-object (string, number, etc.) is passed it can be accessed by the argument number beginning with 1 (as with regex tokens). Multiple objects can be passed and will be merged together.
3510
+ * @example
3511
+ *
3512
+ * 'Welcome, Mr. {name}.'.assign({ name: 'Franklin' }) -> 'Welcome, Mr. Franklin.'
3513
+ * 'You are {1} years old today.'.assign(14) -> 'You are 14 years old today.'
3514
+ * '{n} and {r}'.assign({ n: 'Cheech' }, { r: 'Chong' }) -> 'Cheech and Chong'
3515
+ *
3516
+ ***/
3517
+ 'assign': function() {
3518
+ var assign = object.extended();
3519
+ multiArgs(arguments, function(a, i) {
3520
+ if(object.isObject(a)) {
3521
+ assign.merge(a);
3522
+ } else {
3523
+ assign[i + 1] = a;
3524
+ }
3525
+ });
3526
+ return this.replace(/\{(.+?)\}/g, function(m, key) {
3527
+ return hasOwnProperty(assign, key) ? assign[key] : m;
3528
+ });
3529
+ }
3530
+
3531
+ });
3532
+
3533
+
3534
+ extend(string, true, function(s) { return object.isRegExp(s); }, {
3535
+
3536
+ /*
3537
+ * Many thanks to Steve Levithan here for a ton of inspiration and work dealing with
3538
+ * cross browser Regex splitting. http://blog.stevenlevithan.com/archives/cross-browser-split
3539
+ */
3540
+
3541
+ /***
3542
+ * @method split([separator], [limit])
3543
+ * @returns Array
3544
+ * @short Splits the string by [separator] into an Array.
3545
+ * @extra This method is native to Javascript, but Sugar patches it to provide cross-browser reliability when splitting on a regex.
3546
+ * @example
3547
+ *
3548
+ * 'comma,separated,values'.split(',') -> ['comma','separated','values']
3549
+ * 'a,b|c>d'.split(/[,|>]/) -> ['multi','separated','values']
3550
+ *
3551
+ ***/
3552
+ 'split': function(separator, limit) {
3553
+ var output = [];
3554
+ var lastLastIndex = 0;
3555
+ var separator = regexp(separator).addFlag('g'); // make `global` and avoid `lastIndex` issues by working with a copy
3556
+ var separator2, match, lastIndex, lastLength;
3557
+ if(!regexp.NPCGSupport) {
3558
+ separator2 = RegExp("^" + separator.source + "$(?!\\s)", separator.getFlags()); // doesn't need /g or /y, but they don't hurt
3559
+ }
3560
+ if(isUndefined(limit) || limit < 0) {
3561
+ limit = Infinity;
3562
+ } else {
3563
+ limit = limit | 0;
3564
+ if(!limit) return [];
3565
+ }
3566
+
3567
+ while (match = separator.exec(this)) {
3568
+ lastIndex = match.index + match[0].length; // `separator.lastIndex` is not reliable cross-browser
3569
+ if(lastIndex > lastLastIndex) {
3570
+ output.push(this.slice(lastLastIndex, match.index));
3571
+ // fix browsers whose `exec` methods don't consistently return `undefined` for nonparticipating capturing groups
3572
+ if(!regexp.NPCGSupport && match.length > 1) {
3573
+ match[0].replace(separator2, function () {
3574
+ for (var i = 1; i < arguments.length - 2; i++) {
3575
+ if(isUndefined(arguments[i])) {
3576
+ match[i] = Undefined;
3577
+ }
3578
+ }
3579
+ });
3580
+ }
3581
+ if(match.length > 1 && match.index < this.length) {
3582
+ array.prototype.push.apply(output, match.slice(1));
3583
+ }
3584
+ lastLength = match[0].length;
3585
+ lastLastIndex = lastIndex;
3586
+ if(output.length >= limit) {
3587
+ break;
3588
+ }
3589
+ }
3590
+ if(separator.lastIndex === match.index) {
3591
+ separator.lastIndex++; // avoid an infinite loop
3592
+ }
3593
+ }
3594
+ if(lastLastIndex === this.length) {
3595
+ if(lastLength || !separator.test('')) output.push('');
3596
+ } else {
3597
+ output.push(this.slice(lastLastIndex));
3598
+ }
3599
+ return output.length > limit ? output.slice(0, limit) : output;
3600
+ }
3601
+
3602
+ });
3603
+
3604
+
3605
+
3606
+
3607
+ // Aliases
3608
+
3609
+ extend(string, true, false, {
3610
+
3611
+ /***
3612
+ * @method insert()
3613
+ * @alias add
3614
+ *
3615
+ ***/
3616
+ 'insert': string.prototype.add
3617
+ });
3618
+
3619
+
3620
+
3621
+
3622
+
3623
+
3624
+ /***
3625
+ * RegExp module
3626
+ *
3627
+ * Note here that methods on the RegExp class like .exec and .test will fail in the current version of SpiderMonkey being
3628
+ * used by CouchDB when using shorthand regex notation like /foo/. This is the reason for the intermixed use of shorthand
3629
+ * and compiled regexes here. If you're using JS in CouchDB, it is safer to ALWAYS compile your regexes from a string.
3630
+ *
3631
+ ***/
3632
+
3633
+ regexp.NPCGSupport = isUndefined(regexp('()??').exec('')[1]); // NPCG: nonparticipating capturing group
3634
+
3635
+ function getFlags(reg, flag) {
3636
+ var flags = '';
3637
+ if(flag == 'g' || reg.global) flags += 'g';
3638
+ if(flag == 'i' || reg.ignoreCase) flags += 'i';
3639
+ if(flag == 'm' || reg.multiline) flags += 'm';
3640
+ if(flag == 'y' || reg.sticky) flags += 'y';
3641
+ return flags;
3642
+ }
3643
+
3644
+ extend(regexp, false, false, {
3645
+
3646
+ /***
3647
+ * @method RegExp.escape(<str> = '')
3648
+ * @returns String
3649
+ * @short Escapes all RegExp tokens in a string.
3650
+ * @example
3651
+ *
3652
+ * RegExp.escape('really?') -> 'really\?'
3653
+ * RegExp.escape('yes.') -> 'yes\.'
3654
+ * RegExp.escape('(not really)') -> '\(not really\)'
3655
+ *
3656
+ ***/
3657
+ 'escape': function(str) {
3658
+ if(!object.isString(str)) str = String(str);
3659
+ return str.replace(/([\\/'*+?|()\[\]{}.^$])/g,'\\$1');
3660
+ }
3661
+
3662
+ });
3663
+
3664
+ extend(regexp, true, false, {
3665
+
3666
+ /***
3667
+ * @method getFlags()
3668
+ * @returns String
3669
+ * @short Returns the flags of the regex as a string.
3670
+ * @example
3671
+ *
3672
+ * /texty/gim.getFlags('testy') -> 'gim'
3673
+ *
3674
+ ***/
3675
+ 'getFlags': function() {
3676
+ return getFlags(this);
3677
+ },
3678
+
3679
+ /***
3680
+ * @method setFlags(<flags>)
3681
+ * @returns RegExp
3682
+ * @short Sets the flags on a regex and retuns a copy.
3683
+ * @example
3684
+ *
3685
+ * /texty/.setFlags('gim') -> now has global, ignoreCase, and multiline set
3686
+ *
3687
+ ***/
3688
+ 'setFlags': function(flags) {
3689
+ return regexp(this.source, flags);
3690
+ },
3691
+
3692
+ /***
3693
+ * @method addFlag(<flag>)
3694
+ * @returns RegExp
3695
+ * @short Adds <flag> to the regex.
3696
+ * @example
3697
+ *
3698
+ * /texty/.addFlag('g') -> now has global flag set
3699
+ *
3700
+ ***/
3701
+ 'addFlag': function(flag) {
3702
+ return this.setFlags(getFlags(this, flag));
3703
+ },
3704
+
3705
+ /***
3706
+ * @method removeFlag(<flag>)
3707
+ * @returns RegExp
3708
+ * @short Removes <flag> from the regex.
3709
+ * @example
3710
+ *
3711
+ * /texty/g.removeFlag('g') -> now has global flag removed
3712
+ *
3713
+ ***/
3714
+ 'removeFlag': function(flag) {
3715
+ return this.setFlags(getFlags(this).replace(flag, ''));
3716
+ }
3717
+
3718
+ });
3719
+
3720
+
3721
+
3722
+
3723
+ /***
3724
+ * Function module
3725
+ *
3726
+ ***/
3727
+
3728
+ function setDelay(fn, ms, after, scope, args) {
3729
+ if(!fn.timers) fn.timers = [];
3730
+ if(!object.isNumber(ms)) ms = 0;
3731
+ fn.timers.push(setTimeout(function(){
3732
+ fn.timers.removeAt(index);
3733
+ after.apply(scope, args || []);
3734
+ }, ms));
3735
+ var index = fn.timers.length;
3736
+ }
3737
+
3738
+ function buildBind() {
3739
+ var support = false;
3740
+ if(Function.prototype.bind) {
3741
+ function F() {};
3742
+ var B = F.bind();
3743
+ support = (new B instanceof B) && !(new F instanceof B);
3744
+ }
3745
+ extend(Function, true, !support, {
3746
+
3747
+ /***
3748
+ * @method bind(<scope>, [arg1], ...)
3749
+ * @returns Function
3750
+ * @short Binds <scope> as the %this% object for the function when it is called. Also allows currying an unlimited number of parameters.
3751
+ * @extra "currying" means setting parameters ([arg1], [arg2], etc.) ahead of time so that they are passed when the function is called later. If you pass additional parameters when the function is actually called, they will be added will be added to the end of the curried parameters.
3752
+ * @example
3753
+ *
3754
+ + (function() {
3755
+ * return this;
3756
+ * }).bind('woof')(); -> returns 'woof'; function is bound with 'woof' as the this object.
3757
+ * (function(a) {
3758
+ * return a;
3759
+ * }).bind(1, 2)(); -> returns 2; function is bound with 1 as the this object and 2 curried as the first parameter
3760
+ * (function(a, b) {
3761
+ * return a + b;
3762
+ * }).bind(1, 2)(3); -> returns 5; function is bound with 1 as the this object, 2 curied as the first parameter and 3 passed as the second when calling the function
3763
+ *
3764
+ ***/
3765
+ 'bind': function(scope) {
3766
+ var fn = this, args = getArgs(arguments, 1), nop, bound;
3767
+ if(!object.isFunction(this)) {
3768
+ throw new TypeError('Function.prototype.bind called on a non-function');
3769
+ }
3770
+ bound = function() {
3771
+ return fn.apply(fn.prototype && this instanceof fn ? this : scope, args.concat(getArgs(arguments)));
3772
+ }
3773
+ nop = function() {};
3774
+ nop.prototype = this.prototype;
3775
+ bound.prototype = new nop();
3776
+ return bound;
3777
+ }
3778
+
3779
+ });
3780
+ }
3781
+
3782
+ function buildFunction() {
3783
+ buildBind();
3784
+ }
3785
+
3786
+
3787
+ extend(Function, true, false, {
3788
+
3789
+ /***
3790
+ * @method lazy([ms] = 1, [limit] = Infinity)
3791
+ * @returns Function
3792
+ * @short Creates lazy functions for non-blocking operations.
3793
+ * @extra This method will wrap the function inside another that, when executed repeatedly in a loop, will execute [ms] milliseconds after the last iteration (a.k.a. "function throttling"). By passing in a smaller value for [ms] (can be a decimal < 1), you can "tighen up" the execution time so that the iterations happen faster. By passing in a larger value for [ms], you can space the function execution out to prevent thread blocking. Playing with this number is the easiest way to strike a balance for heavier operations. Calls to lazy functions beyond [limit], if it is set to a finite number, will be ignored if other calls are waiting. For example if [limit] is 50 and 50 calls are queued, any subsequent call will be ignored until the number of queued calls goes down to < 50 again. This prevents lazy functions from being hammered too hard. Additionally, lazy functions can be canceled during execution using the %cancel% method, which will clear the entire queue.
3794
+ * @example
3795
+ *
3796
+ * (function() {
3797
+ * // Executes immediately.
3798
+ * }).lazy()();
3799
+ * (3).times(function() {
3800
+ * // Executes 3 times, with each execution 20ms later than the last.
3801
+ * }.lazy(20));
3802
+ * (100).times(function() {
3803
+ * // Executes 50 times, with each execution 20ms later than the last.
3804
+ * }.lazy(20, 50));
3805
+ *
3806
+ ***/
3807
+ 'lazy': function(ms, limit) {
3808
+ var fn = this, queue = [], lock = false, execute, rounded, perExecution;
3809
+ ms = ms || 1;
3810
+ limit = limit || Infinity;
3811
+ rounded = ms.ceil();
3812
+ perExecution = round(rounded / ms);
3813
+ execute = function() {
3814
+ if(lock || queue.length == 0) return;
3815
+ var max = Math.max(queue.length - perExecution, 0);
3816
+ while(queue.length > max) {
3817
+ // Getting uber-meta here...
3818
+ Function.prototype.apply.apply(fn, queue.shift());
3819
+ }
3820
+ setDelay(lazy, rounded, function() {
3821
+ lock = false;
3822
+ execute();
3823
+ });
3824
+ lock = true;
3825
+ }
3826
+ function lazy() {
3827
+ // The first call is immediate, so having 1 in the queue
3828
+ // implies two calls have already taken place.
3829
+ if(lock && queue.length > limit - 2) return;
3830
+ queue.push([this, arguments]);
3831
+ execute();
3832
+ }
3833
+ return lazy;
3834
+ },
3835
+
3836
+ /***
3837
+ * @method delay([ms] = 1, [arg1], ...)
3838
+ * @returns Function
3839
+ * @short Executes the function after <ms> milliseconds.
3840
+ * @extra Returns a reference to itself. %delay% is also a way to execute non-blocking operations that will wait until the CPU is free. Delayed functions can be canceled using the %cancel% method. Can also curry arguments passed in after <ms>.
3841
+ * @example
3842
+ *
3843
+ * (function(arg1) {
3844
+ * // called 1s later
3845
+ * }).delay(1000, 'arg1');
3846
+ *
3847
+ ***/
3848
+ 'delay': function(ms) {
3849
+ var fn = this;
3850
+ var args = getArgs(arguments, 1);
3851
+ setDelay(fn, ms, fn, fn, args);
3852
+ return fn;
3853
+ },
3854
+
3855
+ /***
3856
+ * @method throttle(<ms>)
3857
+ * @returns Function
3858
+ * @short Creates a throttled version of the function that will only be executed once per <ms> milliseconds.
3859
+ * @example
3860
+ *
3861
+ * var fn = (function(arg1) {
3862
+ * // called immediately and will wait 50ms until it responds again
3863
+ * }).throttle(50); fn() fn() fn();
3864
+ *
3865
+ ***/
3866
+ 'throttle': function(ms) {
3867
+ return this.lazy(ms, 1);
3868
+ },
3869
+
3870
+ /***
3871
+ * @method debounce(<ms>)
3872
+ * @returns Function
3873
+ * @short Creates a "debounced" function that postpones its execution until after <ms> milliseconds have passed.
3874
+ * @extra This method is useful to execute a function after things have "settled down". A good example of this is when a user tabs quickly through form fields, execution of a heavy operation should happen after a few milliseconds when they have "settled" on a field.
3875
+ * @example
3876
+ *
3877
+ * var fn = (function(arg1) {
3878
+ * // called once 50ms later
3879
+ * }).debounce(50); fn() fn() fn();
3880
+ *
3881
+ ***/
3882
+ 'debounce': function(ms) {
3883
+ var fn = this;
3884
+ return function() {
3885
+ fn.cancel();
3886
+ setDelay(fn, ms, fn, this, arguments);
3887
+ }
3888
+ },
3889
+
3890
+ /***
3891
+ * @method cancel()
3892
+ * @returns Function
3893
+ * @short Cancels a delayed function scheduled to be run.
3894
+ * @extra %delay%, %lazy%, and %debounce% can all set delays. Note that this method won't work when using certain other frameworks like Prototype, as they will retain their %delay% method.
3895
+ * @example
3896
+ *
3897
+ * (function() {
3898
+ * alert('hay'); // Never called
3899
+ * }).delay(500).cancel();
3900
+ *
3901
+ ***/
3902
+ 'cancel': function() {
3903
+ if(object.isArray(this.timers)) {
3904
+ while(this.timers.length > 0) {
3905
+ clearTimeout(this.timers.shift());
3906
+ }
3907
+ }
3908
+ return this;
3909
+ },
3910
+
3911
+ /***
3912
+ * @method after([num] = 1)
3913
+ * @returns Function
3914
+ * @short Creates a function that will execute after [num] calls.
3915
+ * @extra %after% is useful for running a final callback after a series of asynchronous operations, when the order in which the operations will complete is unknown.
3916
+ * @example
3917
+ *
3918
+ * var fn = (function() {
3919
+ * // Will be executed once only
3920
+ * }).after(3); fn(); fn(); fn();
3921
+ *
3922
+ ***/
3923
+ 'after': function(num) {
3924
+ var fn = this, counter = 0, storedArguments = [];
3925
+ if(!object.isNumber(num)) {
3926
+ num = 1;
3927
+ } else if(num === 0) {
3928
+ fn.call();
3929
+ return fn;
3930
+ }
3931
+ return function() {
3932
+ var ret;
3933
+ storedArguments.push(Array.create(arguments));
3934
+ counter++;
3935
+ if(counter == num) {
3936
+ ret = fn.call(this, storedArguments);
3937
+ counter = 0;
3938
+ storedArguments = [];
3939
+ return ret;
3940
+ }
3941
+ }
3942
+ },
3943
+
3944
+ /***
3945
+ * @method once()
3946
+ * @returns Function
3947
+ * @short Creates a function that will execute only once and store the result.
3948
+ * @extra %once% is useful for creating functions that will cache the result of an expensive operation and use it on subsequent calls. Also it can be useful for creating initialization functions that only need to be run once.
3949
+ * @example
3950
+ *
3951
+ * var fn = (function() {
3952
+ * // Will be executed once only
3953
+ * }).once(); fn(); fn(); fn();
3954
+ *
3955
+ ***/
3956
+ 'once': function() {
3957
+ var fn = this;
3958
+ return function() {
3959
+ return hasOwnProperty(fn, 'memo') ? fn['memo'] : fn['memo'] = fn.apply(this, arguments);
3960
+ }
3961
+ },
3962
+
3963
+ /***
3964
+ * @method fill(<arg1>, <arg2>, ...)
3965
+ * @returns Function
3966
+ * @short Returns a new version of the function which when called will have some of its arguments pre-emptively filled in, also known as "currying".
3967
+ * @extra Arguments passed to a "filled" function are generally appended to the curried arguments. However, if %undefined% is passed as any of the arguments to %fill%, it will be replaced, when the "filled" function is executed. This allows currying of arguments even when they occur toward the end of an argument list (the example demonstrates this much more clearly).
3968
+ * @example
3969
+ *
3970
+ * var delayOneSecond = setTimeout.fill(undefined, 1000);
3971
+ * delayOneSecond(function() {
3972
+ * // Will be executed 1s later
3973
+ * });
3974
+ *
3975
+ ***/
3976
+ 'fill': function() {
3977
+ var fn = this, curried = getArgs(arguments);
3978
+ return function() {
3979
+ var args = getArgs(arguments);
3980
+ arrayEach(curried, function(arg, index) {
3981
+ if(arg != null || index >= args.length) args.splice(index, 0, arg);
3982
+ });
3983
+ return fn.apply(this, args);
3984
+ }
3985
+ }
3986
+
3987
+
3988
+ });
3989
+
3990
+
3991
+ // Initialize
3992
+ buildObject();
3993
+ buildString();
3994
+ buildFunction();
3995
+ buildArray();
3996
+ initializeClass(date);
3997
+
3998
+ Object.initializeClass = initializeClass;
3999
+
4000
+
4001
+ })();