Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(102)

Side by Side Diff: Tools/GardeningServer/scripts/third_party/sugar.js

Issue 359283003: Remove usages of jquery and add sugar.js from garden-o-matic. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: remove jquery script from ct-sheriff-o-matic Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 /*
2 * Sugar Library v1.4.1
3 *
4 * Freely distributable and licensed under the MIT-style license.
5 * Copyright (c) 2013 Andrew Plummer
6 * http://sugarjs.com/
7 *
8 * ---------------------------- */
9 (function(){
10 /***
11 * @package Core
12 * @description Internal utility and common methods.
13 ***/
14
15
16 // A few optimizations for Google Closure Compiler will save us a couple kb in the release script.
17 var object = Object, array = Array, regexp = RegExp, date = Date, string = Str ing, number = Number, math = Math, Undefined;
18
19 // The global context
20 var globalContext = typeof global !== 'undefined' ? global : this;
21
22 // Internal toString
23 var internalToString = object.prototype.toString;
24
25 // Internal hasOwnProperty
26 var internalHasOwnProperty = object.prototype.hasOwnProperty;
27
28 // defineProperty exists in IE8 but will error when trying to define a propert y on
29 // native objects. IE8 does not have defineProperies, however, so this check s aves a try/catch block.
30 var definePropertySupport = object.defineProperty && object.defineProperties;
31
32 // Are regexes type function?
33 var regexIsFunction = typeof regexp() === 'function';
34
35 // Do strings have no keys?
36 var noKeysInStringObjects = !('0' in new string('a'));
37
38 // Type check methods need a way to be accessed dynamically.
39 var typeChecks = {};
40
41 // Classes that can be matched by value
42 var matchedByValueReg = /^\[object Date|Array|String|Number|RegExp|Boolean|Arg uments\]$/;
43
44 // Class initializers and class helpers
45 var ClassNames = 'Boolean,Number,String,Array,Date,RegExp,Function'.split(',') ;
46
47 var isBoolean = buildPrimitiveClassCheck('boolean', ClassNames[0]);
48 var isNumber = buildPrimitiveClassCheck('number', ClassNames[1]);
49 var isString = buildPrimitiveClassCheck('string', ClassNames[2]);
50
51 var isArray = buildClassCheck(ClassNames[3]);
52 var isDate = buildClassCheck(ClassNames[4]);
53 var isRegExp = buildClassCheck(ClassNames[5]);
54
55
56 // Wanted to enhance performance here by using simply "typeof"
57 // but Firefox has two major issues that make this impossible,
58 // one fixed, the other not. Despite being typeof "function"
59 // the objects below still report in as [object Function], so
60 // we need to perform a full class check here.
61 //
62 // 1. Regexes can be typeof "function" in FF < 3
63 // https://bugzilla.mozilla.org/show_bug.cgi?id=61911 (fixed)
64 //
65 // 2. HTMLEmbedElement and HTMLObjectElement are be typeof "function"
66 // https://bugzilla.mozilla.org/show_bug.cgi?id=268945 (won't fix)
67 //
68 var isFunction = buildClassCheck(ClassNames[6]);
69
70 function isClass(obj, klass, cached) {
71 var k = cached || className(obj);
72 return k === '[object '+klass+']';
73 }
74
75 function buildClassCheck(klass) {
76 var fn = (klass === 'Array' && array.isArray) || function(obj, cached) {
77 return isClass(obj, klass, cached);
78 };
79 typeChecks[klass] = fn;
80 return fn;
81 }
82
83 function buildPrimitiveClassCheck(type, klass) {
84 var fn = function(obj) {
85 if(isObjectType(obj)) {
86 return isClass(obj, klass);
87 }
88 return typeof obj === type;
89 }
90 typeChecks[klass] = fn;
91 return fn;
92 }
93
94 function className(obj) {
95 return internalToString.call(obj);
96 }
97
98 function initializeClasses() {
99 initializeClass(object);
100 iterateOverObject(ClassNames, function(i,name) {
101 initializeClass(globalContext[name]);
102 });
103 }
104
105 function initializeClass(klass) {
106 if(klass['SugarMethods']) return;
107 defineProperty(klass, 'SugarMethods', {});
108 extend(klass, false, true, {
109 'extend': function(methods, override, instance) {
110 extend(klass, instance !== false, override, methods);
111 },
112 'sugarRestore': function() {
113 return batchMethodExecute(this, klass, arguments, function(target, name, m) {
114 defineProperty(target, name, m.method);
115 });
116 },
117 'sugarRevert': function() {
118 return batchMethodExecute(this, klass, arguments, function(target, name, m) {
119 if(m['existed']) {
120 defineProperty(target, name, m['original']);
121 } else {
122 delete target[name];
123 }
124 });
125 }
126 });
127 }
128
129 // Class extending methods
130
131 function extend(klass, instance, override, methods) {
132 var extendee = instance ? klass.prototype : klass;
133 initializeClass(klass);
134 iterateOverObject(methods, function(name, extendedFn) {
135 var nativeFn = extendee[name],
136 existed = hasOwnProperty(extendee, name);
137 if(isFunction(override) && nativeFn) {
138 extendedFn = wrapNative(nativeFn, extendedFn, override);
139 }
140 if(override !== false || !nativeFn) {
141 defineProperty(extendee, name, extendedFn);
142 }
143 // If the method is internal to Sugar, then
144 // store a reference so it can be restored later.
145 klass['SugarMethods'][name] = {
146 'method': extendedFn,
147 'existed': existed,
148 'original': nativeFn,
149 'instance': instance
150 };
151 });
152 }
153
154 function extendSimilar(klass, instance, override, set, fn) {
155 var methods = {};
156 set = isString(set) ? set.split(',') : set;
157 set.forEach(function(name, i) {
158 fn(methods, name, i);
159 });
160 extend(klass, instance, override, methods);
161 }
162
163 function batchMethodExecute(target, klass, args, fn) {
164 var all = args.length === 0, methods = multiArgs(args), changed = false;
165 iterateOverObject(klass['SugarMethods'], function(name, m) {
166 if(all || methods.indexOf(name) !== -1) {
167 changed = true;
168 fn(m['instance'] ? target.prototype : target, name, m);
169 }
170 });
171 return changed;
172 }
173
174 function wrapNative(nativeFn, extendedFn, condition) {
175 return function(a) {
176 return condition.apply(this, arguments) ?
177 extendedFn.apply(this, arguments) :
178 nativeFn.apply(this, arguments);
179 }
180 }
181
182 function defineProperty(target, name, method) {
183 if(definePropertySupport) {
184 object.defineProperty(target, name, {
185 'value': method,
186 'configurable': true,
187 'enumerable': false,
188 'writable': true
189 });
190 } else {
191 target[name] = method;
192 }
193 }
194
195
196 // Argument helpers
197
198 function multiArgs(args, fn, from) {
199 var result = [], i = from || 0, len;
200 for(len = args.length; i < len; i++) {
201 result.push(args[i]);
202 if(fn) fn.call(args, args[i], i);
203 }
204 return result;
205 }
206
207 function flattenedArgs(args, fn, from) {
208 var arg = args[from || 0];
209 if(isArray(arg)) {
210 args = arg;
211 from = 0;
212 }
213 return multiArgs(args, fn, from);
214 }
215
216 function checkCallback(fn) {
217 if(!fn || !fn.call) {
218 throw new TypeError('Callback is not callable');
219 }
220 }
221
222
223 // General helpers
224
225 function isDefined(o) {
226 return o !== Undefined;
227 }
228
229 function isUndefined(o) {
230 return o === Undefined;
231 }
232
233
234 // Object helpers
235
236 function hasProperty(obj, prop) {
237 return !isPrimitiveType(obj) && prop in obj;
238 }
239
240 function hasOwnProperty(obj, prop) {
241 return !!obj && internalHasOwnProperty.call(obj, prop);
242 }
243
244 function isObjectType(obj) {
245 // 1. Check for null
246 // 2. Check for regexes in environments where they are "functions".
247 return !!obj && (typeof obj === 'object' || (regexIsFunction && isRegExp(obj )));
248 }
249
250 function isPrimitiveType(obj) {
251 var type = typeof obj;
252 return obj == null || type === 'string' || type === 'number' || type === 'bo olean';
253 }
254
255 function isPlainObject(obj, klass) {
256 klass = klass || className(obj);
257 try {
258 // Not own constructor property must be Object
259 // This code was borrowed from jQuery.isPlainObject
260 if (obj && obj.constructor &&
261 !hasOwnProperty(obj, 'constructor') &&
262 !hasOwnProperty(obj.constructor.prototype, 'isPrototypeOf')) {
263 return false;
264 }
265 } catch (e) {
266 // IE8,9 Will throw exceptions on certain host objects.
267 return false;
268 }
269 // === on the constructor is not safe across iframes
270 // 'hasOwnProperty' ensures that the object also inherits
271 // from Object, which is false for DOMElements in IE.
272 return !!obj && klass === '[object Object]' && 'hasOwnProperty' in obj;
273 }
274
275 function iterateOverObject(obj, fn) {
276 var key;
277 for(key in obj) {
278 if(!hasOwnProperty(obj, key)) continue;
279 if(fn.call(obj, key, obj[key], obj) === false) break;
280 }
281 }
282
283 function simpleRepeat(n, fn) {
284 for(var i = 0; i < n; i++) {
285 fn(i);
286 }
287 }
288
289 function simpleMerge(target, source) {
290 iterateOverObject(source, function(key) {
291 target[key] = source[key];
292 });
293 return target;
294 }
295
296 // Make primtives types like strings into objects.
297 function coercePrimitiveToObject(obj) {
298 if(isPrimitiveType(obj)) {
299 obj = object(obj);
300 }
301 if(noKeysInStringObjects && isString(obj)) {
302 forceStringCoercion(obj);
303 }
304 return obj;
305 }
306
307 // Force strings to have their indexes set in
308 // environments that don't do this automatically.
309 function forceStringCoercion(obj) {
310 var i = 0, chr;
311 while(chr = obj.charAt(i)) {
312 obj[i++] = chr;
313 }
314 }
315
316 // Hash definition
317
318 function Hash(obj) {
319 simpleMerge(this, coercePrimitiveToObject(obj));
320 };
321
322 Hash.prototype.constructor = object;
323
324 // Math helpers
325
326 var abs = math.abs;
327 var pow = math.pow;
328 var ceil = math.ceil;
329 var floor = math.floor;
330 var round = math.round;
331 var min = math.min;
332 var max = math.max;
333
334 function withPrecision(val, precision, fn) {
335 var multiplier = pow(10, abs(precision || 0));
336 fn = fn || round;
337 if(precision < 0) multiplier = 1 / multiplier;
338 return fn(val * multiplier) / multiplier;
339 }
340
341 // Full width number helpers
342
343 var HalfWidthZeroCode = 0x30;
344 var HalfWidthNineCode = 0x39;
345 var FullWidthZeroCode = 0xff10;
346 var FullWidthNineCode = 0xff19;
347
348 var HalfWidthPeriod = '.';
349 var FullWidthPeriod = '.';
350 var HalfWidthComma = ',';
351
352 // Used here and later in the Date package.
353 var FullWidthDigits = '';
354
355 var NumberNormalizeMap = {};
356 var NumberNormalizeReg;
357
358 function codeIsNumeral(code) {
359 return (code >= HalfWidthZeroCode && code <= HalfWidthNineCode) ||
360 (code >= FullWidthZeroCode && code <= FullWidthNineCode);
361 }
362
363 function buildNumberHelpers() {
364 var digit, i;
365 for(i = 0; i <= 9; i++) {
366 digit = chr(i + FullWidthZeroCode);
367 FullWidthDigits += digit;
368 NumberNormalizeMap[digit] = chr(i + HalfWidthZeroCode);
369 }
370 NumberNormalizeMap[HalfWidthComma] = '';
371 NumberNormalizeMap[FullWidthPeriod] = HalfWidthPeriod;
372 // Mapping this to itself to easily be able to easily
373 // capture it in stringToNumber to detect decimals later.
374 NumberNormalizeMap[HalfWidthPeriod] = HalfWidthPeriod;
375 NumberNormalizeReg = regexp('[' + FullWidthDigits + FullWidthPeriod + HalfWi dthComma + HalfWidthPeriod + ']', 'g');
376 }
377
378 // String helpers
379
380 function chr(num) {
381 return string.fromCharCode(num);
382 }
383
384 // WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in th e Space, Separator category.
385 function getTrimmableCharacters() {
386 return '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u 2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u30 00\uFEFF';
387 }
388
389 function repeatString(str, num) {
390 var result = '', str = str.toString();
391 while (num > 0) {
392 if (num & 1) {
393 result += str;
394 }
395 if (num >>= 1) {
396 str += str;
397 }
398 }
399 return result;
400 }
401
402 // Returns taking into account full-width characters, commas, and decimals.
403 function stringToNumber(str, base) {
404 var sanitized, isDecimal;
405 sanitized = str.replace(NumberNormalizeReg, function(chr) {
406 var replacement = NumberNormalizeMap[chr];
407 if(replacement === HalfWidthPeriod) {
408 isDecimal = true;
409 }
410 return replacement;
411 });
412 return isDecimal ? parseFloat(sanitized) : parseInt(sanitized, base || 10);
413 }
414
415
416 // Used by Number and Date
417
418 function padNumber(num, place, sign, base) {
419 var str = abs(num).toString(base || 10);
420 str = repeatString('0', place - str.replace(/\.\d+/, '').length) + str;
421 if(sign || num < 0) {
422 str = (num < 0 ? '-' : '+') + str;
423 }
424 return str;
425 }
426
427 function getOrdinalizedSuffix(num) {
428 if(num >= 11 && num <= 13) {
429 return 'th';
430 } else {
431 switch(num % 10) {
432 case 1: return 'st';
433 case 2: return 'nd';
434 case 3: return 'rd';
435 default: return 'th';
436 }
437 }
438 }
439
440
441 // RegExp helpers
442
443 function getRegExpFlags(reg, add) {
444 var flags = '';
445 add = add || '';
446 function checkFlag(prop, flag) {
447 if(prop || add.indexOf(flag) > -1) {
448 flags += flag;
449 }
450 }
451 checkFlag(reg.multiline, 'm');
452 checkFlag(reg.ignoreCase, 'i');
453 checkFlag(reg.global, 'g');
454 checkFlag(reg.sticky, 'y');
455 return flags;
456 }
457
458 function escapeRegExp(str) {
459 if(!isString(str)) str = string(str);
460 return str.replace(/([\\/\'*+?|()\[\]{}.^$])/g,'\\$1');
461 }
462
463
464 // Date helpers
465
466 function callDateGet(d, method) {
467 return d['get' + (d._utc ? 'UTC' : '') + method]();
468 }
469
470 function callDateSet(d, method, value) {
471 return d['set' + (d._utc && method != 'ISOWeek' ? 'UTC' : '') + method](valu e);
472 }
473
474 // Used by Array#unique and Object.equal
475
476 function stringify(thing, stack) {
477 var type = typeof thing,
478 thingIsObject,
479 thingIsArray,
480 klass, value,
481 arr, key, i, len;
482
483 // Return quickly if string to save cycles
484 if(type === 'string') return thing;
485
486 klass = internalToString.call(thing)
487 thingIsObject = isPlainObject(thing, klass);
488 thingIsArray = isArray(thing, klass);
489
490 if(thing != null && thingIsObject || thingIsArray) {
491 // This method for checking for cyclic structures was egregiously stolen f rom
492 // the ingenious method by @kitcambridge from the Underscore script:
493 // https://github.com/documentcloud/underscore/issues/240
494 if(!stack) stack = [];
495 // Allowing a step into the structure before triggering this
496 // script to save cycles on standard JSON structures and also to
497 // try as hard as possible to catch basic properties that may have
498 // been modified.
499 if(stack.length > 1) {
500 i = stack.length;
501 while (i--) {
502 if (stack[i] === thing) {
503 return 'CYC';
504 }
505 }
506 }
507 stack.push(thing);
508 value = thing.valueOf() + string(thing.constructor);
509 arr = thingIsArray ? thing : object.keys(thing).sort();
510 for(i = 0, len = arr.length; i < len; i++) {
511 key = thingIsArray ? i : arr[i];
512 value += key + stringify(thing[key], stack);
513 }
514 stack.pop();
515 } else if(1 / thing === -Infinity) {
516 value = '-0';
517 } else {
518 value = string(thing && thing.valueOf ? thing.valueOf() : thing);
519 }
520 return type + klass + value;
521 }
522
523 function isEqual(a, b) {
524 if(a === b) {
525 // Return quickly up front when matching by reference,
526 // but be careful about 0 !== -0.
527 return a !== 0 || 1 / a === 1 / b;
528 } else if(objectIsMatchedByValue(a) && objectIsMatchedByValue(b)) {
529 return stringify(a) === stringify(b);
530 }
531 return false;
532 }
533
534 function objectIsMatchedByValue(obj) {
535 // Only known objects are matched by value. This is notably excluding functi ons, DOM Elements, and instances of
536 // user-created classes. The latter can arguably be matched by value, but di stinguishing between these and
537 // host objects -- which should never be compared by value -- is very tricky so not dealing with it here.
538 var klass = className(obj);
539 return matchedByValueReg.test(klass) || isPlainObject(obj, klass);
540 }
541
542
543 // Used by Array#at and String#at
544
545 function getEntriesForIndexes(obj, args, isString) {
546 var result,
547 length = obj.length,
548 argsLen = args.length,
549 overshoot = args[argsLen - 1] !== false,
550 multiple = argsLen > (overshoot ? 1 : 2);
551 if(!multiple) {
552 return entryAtIndex(obj, length, args[0], overshoot, isString);
553 }
554 result = [];
555 multiArgs(args, function(index) {
556 if(isBoolean(index)) return false;
557 result.push(entryAtIndex(obj, length, index, overshoot, isString));
558 });
559 return result;
560 }
561
562 function entryAtIndex(obj, length, index, overshoot, isString) {
563 if(overshoot) {
564 index = index % length;
565 if(index < 0) index = length + index;
566 }
567 return isString ? obj.charAt(index) : obj[index];
568 }
569
570
571 // Object class methods implemented as instance methods
572
573 function buildObjectInstanceMethods(set, target) {
574 extendSimilar(target, true, false, set, function(methods, name) {
575 methods[name + (name === 'equal' ? 's' : '')] = function() {
576 return object[name].apply(null, [this].concat(multiArgs(arguments)));
577 }
578 });
579 }
580
581 initializeClasses();
582 buildNumberHelpers();
583
584
585 /***
586 * @package ES5
587 * @description Shim methods that provide ES5 compatible functionality. This p ackage can be excluded if you do not require legacy browser support (IE8 and bel ow).
588 *
589 ***/
590
591
592 /***
593 * Object module
594 *
595 ***/
596
597 extend(object, false, false, {
598
599 'keys': function(obj) {
600 var keys = [];
601 if(!isObjectType(obj) && !isRegExp(obj) && !isFunction(obj)) {
602 throw new TypeError('Object required');
603 }
604 iterateOverObject(obj, function(key, value) {
605 keys.push(key);
606 });
607 return keys;
608 }
609
610 });
611
612
613 /***
614 * Array module
615 *
616 ***/
617
618 // ECMA5 methods
619
620 function arrayIndexOf(arr, search, fromIndex, increment) {
621 var length = arr.length,
622 fromRight = increment == -1,
623 start = fromRight ? length - 1 : 0,
624 index = toIntegerWithDefault(fromIndex, start);
625 if(index < 0) {
626 index = length + index;
627 }
628 if((!fromRight && index < 0) || (fromRight && index >= length)) {
629 index = start;
630 }
631 while((fromRight && index >= 0) || (!fromRight && index < length)) {
632 if(arr[index] === search) {
633 return index;
634 }
635 index += increment;
636 }
637 return -1;
638 }
639
640 function arrayReduce(arr, fn, initialValue, fromRight) {
641 var length = arr.length, count = 0, defined = isDefined(initialValue), resul t, index;
642 checkCallback(fn);
643 if(length == 0 && !defined) {
644 throw new TypeError('Reduce called on empty array with no initial value');
645 } else if(defined) {
646 result = initialValue;
647 } else {
648 result = arr[fromRight ? length - 1 : count];
649 count++;
650 }
651 while(count < length) {
652 index = fromRight ? length - count - 1 : count;
653 if(index in arr) {
654 result = fn(result, arr[index], index, arr);
655 }
656 count++;
657 }
658 return result;
659 }
660
661 function toIntegerWithDefault(i, d) {
662 if(isNaN(i)) {
663 return d;
664 } else {
665 return parseInt(i >> 0);
666 }
667 }
668
669 function checkFirstArgumentExists(args) {
670 if(args.length === 0) {
671 throw new TypeError('First argument must be defined');
672 }
673 }
674
675
676
677
678 extend(array, false, false, {
679
680 /***
681 *
682 * @method Array.isArray(<obj>)
683 * @returns Boolean
684 * @short Returns true if <obj> is an Array.
685 * @extra This method is provided for browsers that don't support it interna lly.
686 * @example
687 *
688 * Array.isArray(3) -> false
689 * Array.isArray(true) -> false
690 * Array.isArray('wasabi') -> false
691 * Array.isArray([1,2,3]) -> true
692 *
693 ***/
694 'isArray': function(obj) {
695 return isArray(obj);
696 }
697
698 });
699
700
701 extend(array, true, false, {
702
703 /***
704 * @method every(<f>, [scope])
705 * @returns Boolean
706 * @short Returns true if all elements in the array match <f>.
707 * @extra [scope] is the %this% object. %all% is provided an alias. In addit ion to providing this method for browsers that don't support it natively, this m ethod also implements @array_matching.
708 * @example
709 *
710 + ['a','a','a'].every(function(n) {
711 * return n == 'a';
712 * });
713 * ['a','a','a'].every('a') -> true
714 * [{a:2},{a:2}].every({a:2}) -> true
715 ***/
716 'every': function(fn, scope) {
717 var length = this.length, index = 0;
718 checkFirstArgumentExists(arguments);
719 while(index < length) {
720 if(index in this && !fn.call(scope, this[index], index, this)) {
721 return false;
722 }
723 index++;
724 }
725 return true;
726 },
727
728 /***
729 * @method some(<f>, [scope])
730 * @returns Boolean
731 * @short Returns true if any element in the array matches <f>.
732 * @extra [scope] is the %this% object. %any% is provided as an alias. In ad dition to providing this method for browsers that don't support it natively, thi s method also implements @array_matching.
733 * @example
734 *
735 + ['a','b','c'].some(function(n) {
736 * return n == 'a';
737 * });
738 + ['a','b','c'].some(function(n) {
739 * return n == 'd';
740 * });
741 * ['a','b','c'].some('a') -> true
742 * [{a:2},{b:5}].some({a:2}) -> true
743 ***/
744 'some': function(fn, scope) {
745 var length = this.length, index = 0;
746 checkFirstArgumentExists(arguments);
747 while(index < length) {
748 if(index in this && fn.call(scope, this[index], index, this)) {
749 return true;
750 }
751 index++;
752 }
753 return false;
754 },
755
756 /***
757 * @method map(<map>, [scope])
758 * @returns Array
759 * @short Maps the array to another array containing the values that are the result of calling <map> on each element.
760 * @extra [scope] is the %this% object. When <map> is a function, it receive s three arguments: the current element, the current index, and a reference to th e array. 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 short cut for a function that gets that property (or invokes a function) on each eleme nt.
761 * @example
762 *
763 * [1,2,3].map(function(n) {
764 * return n * 3;
765 * }); -> [3,6,9]
766 * ['one','two','three'].map(function(n) {
767 * return n.length;
768 * }); -> [3,3,5]
769 * ['one','two','three'].map('length') -> [3,3,5]
770 *
771 ***/
772 'map': function(fn, scope) {
773 var scope = arguments[1], length = this.length, index = 0, result = new Ar ray(length);
774 checkFirstArgumentExists(arguments);
775 while(index < length) {
776 if(index in this) {
777 result[index] = fn.call(scope, this[index], index, this);
778 }
779 index++;
780 }
781 return result;
782 },
783
784 /***
785 * @method filter(<f>, [scope])
786 * @returns Array
787 * @short Returns any elements in the array that match <f>.
788 * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this method also implements @array _matching.
789 * @example
790 *
791 + [1,2,3].filter(function(n) {
792 * return n > 1;
793 * });
794 * [1,2,2,4].filter(2) -> 2
795 *
796 ***/
797 'filter': function(fn) {
798 var scope = arguments[1];
799 var length = this.length, index = 0, result = [];
800 checkFirstArgumentExists(arguments);
801 while(index < length) {
802 if(index in this && fn.call(scope, this[index], index, this)) {
803 result.push(this[index]);
804 }
805 index++;
806 }
807 return result;
808 },
809
810 /***
811 * @method indexOf(<search>, [fromIndex])
812 * @returns Number
813 * @short Searches the array and returns the first index where <search> occu rs, or -1 if the element is not found.
814 * @extra [fromIndex] is the index from which to begin the search. This meth od 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 %findIndex% met hod instead.
815 * @example
816 *
817 * [1,2,3].indexOf(3) -> 1
818 * [1,2,3].indexOf(7) -> -1
819 *
820 ***/
821 'indexOf': function(search) {
822 var fromIndex = arguments[1];
823 if(isString(this)) return this.indexOf(search, fromIndex);
824 return arrayIndexOf(this, search, fromIndex, 1);
825 },
826
827 /***
828 * @method lastIndexOf(<search>, [fromIndex])
829 * @returns Number
830 * @short Searches the array and returns the last index where <search> occur s, or -1 if the element is not found.
831 * @extra [fromIndex] is the index from which to begin the search. This meth od performs a simple strict equality comparison on <search>.
832 * @example
833 *
834 * [1,2,1].lastIndexOf(1) -> 2
835 * [1,2,1].lastIndexOf(7) -> -1
836 *
837 ***/
838 'lastIndexOf': function(search) {
839 var fromIndex = arguments[1];
840 if(isString(this)) return this.lastIndexOf(search, fromIndex);
841 return arrayIndexOf(this, search, fromIndex, -1);
842 },
843
844 /***
845 * @method forEach([fn], [scope])
846 * @returns Nothing
847 * @short Iterates over the array, calling [fn] on each loop.
848 * @extra This method is only provided for those browsers that do not suppor t it natively. [scope] becomes the %this% object.
849 * @example
850 *
851 * ['a','b','c'].forEach(function(a) {
852 * // Called 3 times: 'a','b','c'
853 * });
854 *
855 ***/
856 'forEach': function(fn) {
857 var length = this.length, index = 0, scope = arguments[1];
858 checkCallback(fn);
859 while(index < length) {
860 if(index in this) {
861 fn.call(scope, this[index], index, this);
862 }
863 index++;
864 }
865 },
866
867 /***
868 * @method reduce(<fn>, [init])
869 * @returns Mixed
870 * @short Reduces the array to a single result.
871 * @extra If [init] is passed as a starting value, that value will be passed as the first argument to the callback. The second argument will be the first el ement in the array. From that point, the result of the callback will then be use d as the first argument of the next iteration. This is often refered to as "accu mulation", and [init] is often called an "accumulator". If [init] is not passed, then <fn> will be called n - 1 times, where n is the length of the array. In th is case, on the first iteration only, the first argument will be the first eleme nt of the array, and the second argument will be the second. After that callback s work as normal, using the result of the previous callback as the first argumen t of the next. This method is only provided for those browsers that do not suppo rt it natively.
872 *
873 * @example
874 *
875 + [1,2,3,4].reduce(function(a, b) {
876 * return a - b;
877 * });
878 + [1,2,3,4].reduce(function(a, b) {
879 * return a - b;
880 * }, 100);
881 *
882 ***/
883 'reduce': function(fn) {
884 return arrayReduce(this, fn, arguments[1]);
885 },
886
887 /***
888 * @method reduceRight([fn], [init])
889 * @returns Mixed
890 * @short Identical to %Array#reduce%, but operates on the elements in rever se order.
891 * @extra This method is only provided for those browsers that do not suppor t it natively.
892 *
893 *
894 *
895 *
896 * @example
897 *
898 + [1,2,3,4].reduceRight(function(a, b) {
899 * return a - b;
900 * });
901 *
902 ***/
903 'reduceRight': function(fn) {
904 return arrayReduce(this, fn, arguments[1], true);
905 }
906
907
908 });
909
910
911
912
913 /***
914 * String module
915 *
916 ***/
917
918
919 function buildTrim() {
920 var support = getTrimmableCharacters().match(/^\s+$/);
921 try { string.prototype.trim.call([1]); } catch(e) { support = false; }
922 extend(string, true, !support, {
923
924 /***
925 * @method trim[Side]()
926 * @returns String
927 * @short Removes leading and/or trailing whitespace from the string.
928 * @extra Whitespace is defined as line breaks, tabs, and any character in the "Space, Separator" Unicode category, conforming to the the ES5 spec. The st andard %trim% method is only added when not fully supported natively.
929 *
930 * @set
931 * trim
932 * trimLeft
933 * trimRight
934 *
935 * @example
936 *
937 * ' wasabi '.trim() -> 'wasabi'
938 * ' wasabi '.trimLeft() -> 'wasabi '
939 * ' wasabi '.trimRight() -> ' wasabi'
940 *
941 ***/
942 'trim': function() {
943 return this.toString().trimLeft().trimRight();
944 },
945
946 'trimLeft': function() {
947 return this.replace(regexp('^['+getTrimmableCharacters()+']+'), '');
948 },
949
950 'trimRight': function() {
951 return this.replace(regexp('['+getTrimmableCharacters()+']+$'), '');
952 }
953 });
954 }
955
956
957
958 /***
959 * Function module
960 *
961 ***/
962
963
964 extend(Function, true, false, {
965
966 /***
967 * @method bind(<scope>, [arg1], ...)
968 * @returns Function
969 * @short Binds <scope> as the %this% object for the function when it is cal led. Also allows currying an unlimited number of parameters.
970 * @extra "currying" means setting parameters ([arg1], [arg2], etc.) ahead o f time so that they are passed when the function is called later. If you pass ad ditional parameters when the function is actually called, they will be added wil l be added to the end of the curried parameters. This method is provided for bro wsers that don't support it internally.
971 * @example
972 *
973 + (function() {
974 * return this;
975 * }).bind('woof')(); -> returns 'woof'; function is bound with 'woof' as the this object.
976 * (function(a) {
977 * return a;
978 * }).bind(1, 2)(); -> returns 2; function is bound with 1 as the this o bject and 2 curried as the first parameter
979 * (function(a, b) {
980 * return a + b;
981 * }).bind(1, 2)(3); -> returns 5; function is bound with 1 as the this o bject, 2 curied as the first parameter and 3 passed as the second when calling t he function
982 *
983 ***/
984 'bind': function(scope) {
985 var fn = this, args = multiArgs(arguments, null, 1), bound;
986 if(!isFunction(this)) {
987 throw new TypeError('Function.prototype.bind called on a non-function');
988 }
989 bound = function() {
990 return fn.apply(fn.prototype && this instanceof fn ? this : scope, args. concat(multiArgs(arguments)));
991 }
992 bound.prototype = this.prototype;
993 return bound;
994 }
995
996 });
997
998 /***
999 * Date module
1000 *
1001 ***/
1002
1003 /***
1004 * @method toISOString()
1005 * @returns String
1006 * @short Formats the string to ISO8601 format.
1007 * @extra This will always format as UTC time. Provided for browsers that do n ot support this method.
1008 * @example
1009 *
1010 * Date.create().toISOString() -> ex. 2011-07-05 12:24:55.528Z
1011 *
1012 ***
1013 * @method toJSON()
1014 * @returns String
1015 * @short Returns a JSON representation of the date.
1016 * @extra This is effectively an alias for %toISOString%. Will always return t he date in UTC time. Provided for browsers that do not support this method.
1017 * @example
1018 *
1019 * Date.create().toJSON() -> ex. 2011-07-05 12:24:55.528Z
1020 *
1021 ***/
1022
1023 extend(date, false, false, {
1024
1025 /***
1026 * @method Date.now()
1027 * @returns String
1028 * @short Returns the number of milliseconds since January 1st, 1970 00:00:0 0 (UTC time).
1029 * @extra Provided for browsers that do not support this method.
1030 * @example
1031 *
1032 * Date.now() -> ex. 1311938296231
1033 *
1034 ***/
1035 'now': function() {
1036 return new date().getTime();
1037 }
1038
1039 });
1040
1041 function buildISOString() {
1042 var d = new date(date.UTC(1999, 11, 31)), target = '1999-12-31T00:00:00.000Z ';
1043 var support = d.toISOString && d.toISOString() === target;
1044 extendSimilar(date, true, !support, 'toISOString,toJSON', function(methods, name) {
1045 methods[name] = function() {
1046 return padNumber(this.getUTCFullYear(), 4) + '-' +
1047 padNumber(this.getUTCMonth() + 1, 2) + '-' +
1048 padNumber(this.getUTCDate(), 2) + 'T' +
1049 padNumber(this.getUTCHours(), 2) + ':' +
1050 padNumber(this.getUTCMinutes(), 2) + ':' +
1051 padNumber(this.getUTCSeconds(), 2) + '.' +
1052 padNumber(this.getUTCMilliseconds(), 3) + 'Z';
1053 }
1054 });
1055 }
1056
1057 // Initialize
1058 buildTrim();
1059 buildISOString();
1060
1061
1062 /***
1063 * @package Array
1064 * @dependency core
1065 * @description Array manipulation and traversal, "fuzzy matching" against ele ments, alphanumeric sorting and collation, enumerable methods on Object.
1066 *
1067 ***/
1068
1069
1070 function regexMatcher(reg) {
1071 reg = regexp(reg);
1072 return function (el) {
1073 return reg.test(el);
1074 }
1075 }
1076
1077 function dateMatcher(d) {
1078 var ms = d.getTime();
1079 return function (el) {
1080 return !!(el && el.getTime) && el.getTime() === ms;
1081 }
1082 }
1083
1084 function functionMatcher(fn) {
1085 return function (el, i, arr) {
1086 // Return true up front if match by reference
1087 return el === fn || fn.call(this, el, i, arr);
1088 }
1089 }
1090
1091 function invertedArgsFunctionMatcher(fn) {
1092 return function (value, key, obj) {
1093 // Return true up front if match by reference
1094 return value === fn || fn.call(obj, key, value, obj);
1095 }
1096 }
1097
1098 function fuzzyMatcher(obj, isObject) {
1099 var matchers = {};
1100 return function (el, i, arr) {
1101 var key;
1102 if(!isObjectType(el)) {
1103 return false;
1104 }
1105 for(key in obj) {
1106 matchers[key] = matchers[key] || getMatcher(obj[key], isObject);
1107 if(matchers[key].call(arr, el[key], i, arr) === false) {
1108 return false;
1109 }
1110 }
1111 return true;
1112 }
1113 }
1114
1115 function defaultMatcher(f) {
1116 return function (el) {
1117 return el === f || isEqual(el, f);
1118 }
1119 }
1120
1121 function getMatcher(f, isObject) {
1122 if(isPrimitiveType(f)) {
1123 // Do nothing and fall through to the
1124 // default matcher below.
1125 } else if(isRegExp(f)) {
1126 // Match against a regexp
1127 return regexMatcher(f);
1128 } else if(isDate(f)) {
1129 // Match against a date. isEqual below should also
1130 // catch this but matching directly up front for speed.
1131 return dateMatcher(f);
1132 } else if(isFunction(f)) {
1133 // Match against a filtering function
1134 if(isObject) {
1135 return invertedArgsFunctionMatcher(f);
1136 } else {
1137 return functionMatcher(f);
1138 }
1139 } else if(isPlainObject(f)) {
1140 // Match against a fuzzy hash or array.
1141 return fuzzyMatcher(f, isObject);
1142 }
1143 // Default is standard isEqual
1144 return defaultMatcher(f);
1145 }
1146
1147 function transformArgument(el, map, context, mapArgs) {
1148 if(!map) {
1149 return el;
1150 } else if(map.apply) {
1151 return map.apply(context, mapArgs || []);
1152 } else if(isFunction(el[map])) {
1153 return el[map].call(el);
1154 } else {
1155 return el[map];
1156 }
1157 }
1158
1159 // Basic array internal methods
1160
1161 function arrayEach(arr, fn, startIndex, loop) {
1162 var index, i, length = +arr.length;
1163 if(startIndex < 0) startIndex = arr.length + startIndex;
1164 i = isNaN(startIndex) ? 0 : startIndex;
1165 if(loop === true) {
1166 length += i;
1167 }
1168 while(i < length) {
1169 index = i % arr.length;
1170 if(!(index in arr)) {
1171 return iterateOverSparseArray(arr, fn, i, loop);
1172 } else if(fn.call(arr, arr[index], index, arr) === false) {
1173 break;
1174 }
1175 i++;
1176 }
1177 }
1178
1179 function iterateOverSparseArray(arr, fn, fromIndex, loop) {
1180 var indexes = [], i;
1181 for(i in arr) {
1182 if(isArrayIndex(arr, i) && i >= fromIndex) {
1183 indexes.push(parseInt(i));
1184 }
1185 }
1186 indexes.sort().each(function(index) {
1187 return fn.call(arr, arr[index], index, arr);
1188 });
1189 return arr;
1190 }
1191
1192 function isArrayIndex(arr, i) {
1193 return i in arr && toUInt32(i) == i && i != 0xffffffff;
1194 }
1195
1196 function toUInt32(i) {
1197 return i >>> 0;
1198 }
1199
1200 function arrayFind(arr, f, startIndex, loop, returnIndex, context) {
1201 var result, index, matcher;
1202 if(arr.length > 0) {
1203 matcher = getMatcher(f);
1204 arrayEach(arr, function(el, i) {
1205 if(matcher.call(context, el, i, arr)) {
1206 result = el;
1207 index = i;
1208 return false;
1209 }
1210 }, startIndex, loop);
1211 }
1212 return returnIndex ? index : result;
1213 }
1214
1215 function arrayUnique(arr, map) {
1216 var result = [], o = {}, transformed;
1217 arrayEach(arr, function(el, i) {
1218 transformed = map ? transformArgument(el, map, arr, [el, i, arr]) : el;
1219 if(!checkForElementInHashAndSet(o, transformed)) {
1220 result.push(el);
1221 }
1222 })
1223 return result;
1224 }
1225
1226 function arrayIntersect(arr1, arr2, subtract) {
1227 var result = [], o = {};
1228 arr2.each(function(el) {
1229 checkForElementInHashAndSet(o, el);
1230 });
1231 arr1.each(function(el) {
1232 var stringified = stringify(el),
1233 isReference = !objectIsMatchedByValue(el);
1234 // Add the result to the array if:
1235 // 1. We're subtracting intersections or it doesn't already exist in the r esult and
1236 // 2. It exists in the compared array and we're adding, or it doesn't exis t and we're removing.
1237 if(elementExistsInHash(o, stringified, el, isReference) !== subtract) {
1238 discardElementFromHash(o, stringified, el, isReference);
1239 result.push(el);
1240 }
1241 });
1242 return result;
1243 }
1244
1245 function arrayFlatten(arr, level, current) {
1246 level = level || Infinity;
1247 current = current || 0;
1248 var result = [];
1249 arrayEach(arr, function(el) {
1250 if(isArray(el) && current < level) {
1251 result = result.concat(arrayFlatten(el, level, current + 1));
1252 } else {
1253 result.push(el);
1254 }
1255 });
1256 return result;
1257 }
1258
1259 function isArrayLike(obj) {
1260 return hasProperty(obj, 'length') && !isString(obj) && !isPlainObject(obj);
1261 }
1262
1263 function isArgumentsObject(obj) {
1264 // .callee exists on Arguments objects in < IE8
1265 return hasProperty(obj, 'length') && (className(obj) === '[object Arguments] ' || !!obj.callee);
1266 }
1267
1268 function flatArguments(args) {
1269 var result = [];
1270 multiArgs(args, function(arg) {
1271 result = result.concat(arg);
1272 });
1273 return result;
1274 }
1275
1276 function elementExistsInHash(hash, key, element, isReference) {
1277 var exists = key in hash;
1278 if(isReference) {
1279 if(!hash[key]) {
1280 hash[key] = [];
1281 }
1282 exists = hash[key].indexOf(element) !== -1;
1283 }
1284 return exists;
1285 }
1286
1287 function checkForElementInHashAndSet(hash, element) {
1288 var stringified = stringify(element),
1289 isReference = !objectIsMatchedByValue(element),
1290 exists = elementExistsInHash(hash, stringified, element, isReferenc e);
1291 if(isReference) {
1292 hash[stringified].push(element);
1293 } else {
1294 hash[stringified] = element;
1295 }
1296 return exists;
1297 }
1298
1299 function discardElementFromHash(hash, key, element, isReference) {
1300 var arr, i = 0;
1301 if(isReference) {
1302 arr = hash[key];
1303 while(i < arr.length) {
1304 if(arr[i] === element) {
1305 arr.splice(i, 1);
1306 } else {
1307 i += 1;
1308 }
1309 }
1310 } else {
1311 delete hash[key];
1312 }
1313 }
1314
1315 // Support methods
1316
1317 function getMinOrMax(obj, map, which, all) {
1318 var el,
1319 key,
1320 edge,
1321 test,
1322 result = [],
1323 max = which === 'max',
1324 min = which === 'min',
1325 isArray = array.isArray(obj);
1326 for(key in obj) {
1327 if(!obj.hasOwnProperty(key)) continue;
1328 el = obj[key];
1329 test = transformArgument(el, map, obj, isArray ? [el, parseInt(key), obj] : []);
1330 if(isUndefined(test)) {
1331 throw new TypeError('Cannot compare with undefined');
1332 }
1333 if(test === edge) {
1334 result.push(el);
1335 } else if(isUndefined(edge) || (max && test > edge) || (min && test < edge )) {
1336 result = [el];
1337 edge = test;
1338 }
1339 }
1340 if(!isArray) result = arrayFlatten(result, 1);
1341 return all ? result : result[0];
1342 }
1343
1344
1345 // Alphanumeric collation helpers
1346
1347 function collateStrings(a, b) {
1348 var aValue, bValue, aChar, bChar, aEquiv, bEquiv, index = 0, tiebreaker = 0;
1349
1350 var sortIgnore = array[AlphanumericSortIgnore];
1351 var sortIgnoreCase = array[AlphanumericSortIgnoreCase];
1352 var sortEquivalents = array[AlphanumericSortEquivalents];
1353 var sortOrder = array[AlphanumericSortOrder];
1354 var naturalSort = array[AlphanumericSortNatural];
1355
1356 a = getCollationReadyString(a, sortIgnore, sortIgnoreCase);
1357 b = getCollationReadyString(b, sortIgnore, sortIgnoreCase);
1358
1359 do {
1360
1361 aChar = getCollationCharacter(a, index, sortEquivalents);
1362 bChar = getCollationCharacter(b, index, sortEquivalents);
1363 aValue = getSortOrderIndex(aChar, sortOrder);
1364 bValue = getSortOrderIndex(bChar, sortOrder);
1365
1366 if(aValue === -1 || bValue === -1) {
1367 aValue = a.charCodeAt(index) || null;
1368 bValue = b.charCodeAt(index) || null;
1369 if(naturalSort && codeIsNumeral(aValue) && codeIsNumeral(bValue)) {
1370 aValue = stringToNumber(a.slice(index));
1371 bValue = stringToNumber(b.slice(index));
1372 }
1373 } else {
1374 aEquiv = aChar !== a.charAt(index);
1375 bEquiv = bChar !== b.charAt(index);
1376 if(aEquiv !== bEquiv && tiebreaker === 0) {
1377 tiebreaker = aEquiv - bEquiv;
1378 }
1379 }
1380 index += 1;
1381 } while(aValue != null && bValue != null && aValue === bValue);
1382 if(aValue === bValue) return tiebreaker;
1383 return aValue - bValue;
1384 }
1385
1386 function getCollationReadyString(str, sortIgnore, sortIgnoreCase) {
1387 if(!isString(str)) str = string(str);
1388 if(sortIgnoreCase) {
1389 str = str.toLowerCase();
1390 }
1391 if(sortIgnore) {
1392 str = str.replace(sortIgnore, '');
1393 }
1394 return str;
1395 }
1396
1397 function getCollationCharacter(str, index, sortEquivalents) {
1398 var chr = str.charAt(index);
1399 return sortEquivalents[chr] || chr;
1400 }
1401
1402 function getSortOrderIndex(chr, sortOrder) {
1403 if(!chr) {
1404 return null;
1405 } else {
1406 return sortOrder.indexOf(chr);
1407 }
1408 }
1409
1410 var AlphanumericSort = 'AlphanumericSort';
1411 var AlphanumericSortOrder = 'AlphanumericSortOrder';
1412 var AlphanumericSortIgnore = 'AlphanumericSortIgnore';
1413 var AlphanumericSortIgnoreCase = 'AlphanumericSortIgnoreCase';
1414 var AlphanumericSortEquivalents = 'AlphanumericSortEquivalents';
1415 var AlphanumericSortNatural = 'AlphanumericSortNatural';
1416
1417
1418
1419 function buildEnhancements() {
1420 var nativeMap = array.prototype.map;
1421 var callbackCheck = function() {
1422 var args = arguments;
1423 return args.length > 0 && !isFunction(args[0]);
1424 };
1425 extendSimilar(array, true, callbackCheck, 'every,all,some,filter,any,none,fi nd,findIndex', function(methods, name) {
1426 var nativeFn = array.prototype[name]
1427 methods[name] = function(f) {
1428 var matcher = getMatcher(f);
1429 return nativeFn.call(this, function(el, index) {
1430 return matcher(el, index, this);
1431 });
1432 }
1433 });
1434 extend(array, true, callbackCheck, {
1435 'map': function(f) {
1436 return nativeMap.call(this, function(el, index) {
1437 return transformArgument(el, f, this, [el, index, this]);
1438 });
1439 }
1440 });
1441 }
1442
1443 function buildAlphanumericSort() {
1444 var order = 'AÁÀÂÃĄBCĆČÇDĎÐEÉÈĚÊËĘFGĞHıIÍÌİÎÏJKLŁMNŃŇÑOÓÒÔPQRŘSŚŠŞTŤUÚÙŮÛÜVW XYÝZŹŻŽÞÆŒØÕÅÄÖ';
1445 var equiv = 'AÁÀÂÃÄ,CÇ,EÉÈÊË,IÍÌİÎÏ,OÓÒÔÕÖ,Sß,UÚÙÛÜ';
1446 array[AlphanumericSortOrder] = order.split('').map(function(str) {
1447 return str + str.toLowerCase();
1448 }).join('');
1449 var equivalents = {};
1450 arrayEach(equiv.split(','), function(set) {
1451 var equivalent = set.charAt(0);
1452 arrayEach(set.slice(1).split(''), function(chr) {
1453 equivalents[chr] = equivalent;
1454 equivalents[chr.toLowerCase()] = equivalent.toLowerCase();
1455 });
1456 });
1457 array[AlphanumericSortNatural] = true;
1458 array[AlphanumericSortIgnoreCase] = true;
1459 array[AlphanumericSortEquivalents] = equivalents;
1460 }
1461
1462 extend(array, false, true, {
1463
1464 /***
1465 *
1466 * @method Array.create(<obj1>, <obj2>, ...)
1467 * @returns Array
1468 * @short Alternate array constructor.
1469 * @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 singl e, 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 in to a standard array.
1470 * @example
1471 *
1472 * Array.create('one', true, 3) -> ['one', true, 3]
1473 * Array.create(['one', true, 3]) -> ['one', true, 3]
1474 + Array.create(function(n) {
1475 * return arguments;
1476 * }('howdy', 'doody'));
1477 *
1478 ***/
1479 'create': function() {
1480 var result = [];
1481 multiArgs(arguments, function(a) {
1482 if(isArgumentsObject(a) || isArrayLike(a)) {
1483 a = array.prototype.slice.call(a, 0);
1484 }
1485 result = result.concat(a);
1486 });
1487 return result;
1488 }
1489
1490 });
1491
1492 extend(array, true, false, {
1493
1494 /***
1495 * @method find(<f>, [context] = undefined)
1496 * @returns Mixed
1497 * @short Returns the first element that matches <f>.
1498 * @extra [context] is the %this% object if passed. When <f> is a function, will use native implementation if it exists. <f> will also match a string, numbe r, array, object, or alternately test against a function or regex. This method i mplements @array_matching.
1499 * @example
1500 *
1501 + [{a:1,b:2},{a:1,b:3},{a:1,b:4}].find(function(n) {
1502 * return n['a'] == 1;
1503 * }); -> {a:1,b:3}
1504 * ['cuba','japan','canada'].find(/^c/) -> 'cuba'
1505 *
1506 ***/
1507 'find': function(f, context) {
1508 checkCallback(f);
1509 return arrayFind(this, f, 0, false, false, context);
1510 },
1511
1512 /***
1513 * @method findIndex(<f>, [context] = undefined)
1514 * @returns Number
1515 * @short Returns the index of the first element that matches <f> or -1 if n ot found.
1516 * @extra [context] is the %this% object if passed. When <f> is a function, will use native implementation if it exists. <f> will also match a string, numbe r, array, object, or alternately test against a function or regex. This method i mplements @array_matching.
1517 *
1518 * @example
1519 *
1520 + [1,2,3,4].findIndex(function(n) {
1521 * return n % 2 == 0;
1522 * }); -> 1
1523 + [1,2,3,4].findIndex(3); -> 2
1524 + ['one','two','three'].findIndex(/t/); -> 1
1525 *
1526 ***/
1527 'findIndex': function(f, context) {
1528 var index;
1529 checkCallback(f);
1530 index = arrayFind(this, f, 0, false, true, context);
1531 return isUndefined(index) ? -1 : index;
1532 }
1533
1534 });
1535
1536 extend(array, true, true, {
1537
1538 /***
1539 * @method findFrom(<f>, [index] = 0, [loop] = false)
1540 * @returns Array
1541 * @short Returns any element that matches <f>, beginning from [index].
1542 * @extra <f> will match a string, number, array, object, or alternately tes t against a function or regex. Will continue from index = 0 if [loop] is true. T his method implements @array_matching.
1543 * @example
1544 *
1545 * ['cuba','japan','canada'].findFrom(/^c/, 2) -> 'canada'
1546 *
1547 ***/
1548 'findFrom': function(f, index, loop) {
1549 return arrayFind(this, f, index, loop);
1550 },
1551
1552 /***
1553 * @method findIndexFrom(<f>, [index] = 0, [loop] = false)
1554 * @returns Array
1555 * @short Returns the index of any element that matches <f>, beginning from [index].
1556 * @extra <f> will match a string, number, array, object, or alternately tes t against a function or regex. Will continue from index = 0 if [loop] is true. T his method implements @array_matching.
1557 * @example
1558 *
1559 * ['cuba','japan','canada'].findIndexFrom(/^c/, 2) -> 2
1560 *
1561 ***/
1562 'findIndexFrom': function(f, index, loop) {
1563 var index = arrayFind(this, f, index, loop, true);
1564 return isUndefined(index) ? -1 : index;
1565 },
1566
1567 /***
1568 * @method findAll(<f>, [index] = 0, [loop] = false)
1569 * @returns Array
1570 * @short Returns all elements that match <f>.
1571 * @extra <f> will match a string, number, array, object, or alternately tes t against a function or regex. Starts at [index], and will continue once from in dex = 0 if [loop] is true. This method implements @array_matching.
1572 * @example
1573 *
1574 + [{a:1,b:2},{a:1,b:3},{a:2,b:4}].findAll(function(n) {
1575 * return n['a'] == 1;
1576 * }); -> [{a:1,b:3},{a:1,b:4}]
1577 * ['cuba','japan','canada'].findAll(/^c/) -> 'cuba','canada'
1578 * ['cuba','japan','canada'].findAll(/^c/, 2) -> 'canada'
1579 *
1580 ***/
1581 'findAll': function(f, index, loop) {
1582 var result = [], matcher;
1583 if(this.length > 0) {
1584 matcher = getMatcher(f);
1585 arrayEach(this, function(el, i, arr) {
1586 if(matcher(el, i, arr)) {
1587 result.push(el);
1588 }
1589 }, index, loop);
1590 }
1591 return result;
1592 },
1593
1594 /***
1595 * @method count(<f>)
1596 * @returns Number
1597 * @short Counts all elements in the array that match <f>.
1598 * @extra <f> will match a string, number, array, object, or alternately tes t against a function or regex. This method implements @array_matching.
1599 * @example
1600 *
1601 * [1,2,3,1].count(1) -> 2
1602 * ['a','b','c'].count(/b/) -> 1
1603 + [{a:1},{b:2}].count(function(n) {
1604 * return n['a'] > 1;
1605 * }); -> 0
1606 *
1607 ***/
1608 'count': function(f) {
1609 if(isUndefined(f)) return this.length;
1610 return this.findAll(f).length;
1611 },
1612
1613 /***
1614 * @method removeAt(<start>, [end])
1615 * @returns Array
1616 * @short Removes element at <start>. If [end] is specified, removes the ran ge between <start> and [end]. This method will change the array! If you don't in tend the array to be changed use %clone% first.
1617 * @example
1618 *
1619 * ['a','b','c'].removeAt(0) -> ['b','c']
1620 * [1,2,3,4].removeAt(1, 3) -> [1]
1621 *
1622 ***/
1623 'removeAt': function(start, end) {
1624 if(isUndefined(start)) return this;
1625 if(isUndefined(end)) end = start;
1626 this.splice(start, end - start + 1);
1627 return this;
1628 },
1629
1630 /***
1631 * @method include(<el>, [index])
1632 * @returns Array
1633 * @short Adds <el> to the array.
1634 * @extra This is a non-destructive alias for %add%. It will not change the original array.
1635 * @example
1636 *
1637 * [1,2,3,4].include(5) -> [1,2,3,4,5]
1638 * [1,2,3,4].include(8, 1) -> [1,8,2,3,4]
1639 * [1,2,3,4].include([5,6,7]) -> [1,2,3,4,5,6,7]
1640 *
1641 ***/
1642 'include': function(el, index) {
1643 return this.clone().add(el, index);
1644 },
1645
1646 /***
1647 * @method exclude([f1], [f2], ...)
1648 * @returns Array
1649 * @short Removes any element in the array that matches [f1], [f2], etc.
1650 * @extra This is a non-destructive alias for %remove%. It will not change t he original array. This method implements @array_matching.
1651 * @example
1652 *
1653 * [1,2,3].exclude(3) -> [1,2]
1654 * ['a','b','c'].exclude(/b/) -> ['a','c']
1655 + [{a:1},{b:2}].exclude(function(n) {
1656 * return n['a'] == 1;
1657 * }); -> [{b:2}]
1658 *
1659 ***/
1660 'exclude': function() {
1661 return array.prototype.remove.apply(this.clone(), arguments);
1662 },
1663
1664 /***
1665 * @method clone()
1666 * @returns Array
1667 * @short Makes a shallow clone of the array.
1668 * @example
1669 *
1670 * [1,2,3].clone() -> [1,2,3]
1671 *
1672 ***/
1673 'clone': function() {
1674 return simpleMerge([], this);
1675 },
1676
1677 /***
1678 * @method unique([map] = null)
1679 * @returns Array
1680 * @short Removes all duplicate elements in the array.
1681 * @extra [map] may be a function mapping the value to be uniqued on or a st ring acting as a shortcut. This is most commonly used when you have a key that e nsures the object's uniqueness, and don't need to check all fields. This method will also correctly operate on arrays of objects.
1682 * @example
1683 *
1684 * [1,2,2,3].unique() -> [1,2,3]
1685 * [{foo:'bar'},{foo:'bar'}].unique() -> [{foo:'bar'}]
1686 + [{foo:'bar'},{foo:'bar'}].unique(function(obj){
1687 * return obj.foo;
1688 * }); -> [{foo:'bar'}]
1689 * [{foo:'bar'},{foo:'bar'}].unique('foo') -> [{foo:'bar'}]
1690 *
1691 ***/
1692 'unique': function(map) {
1693 return arrayUnique(this, map);
1694 },
1695
1696 /***
1697 * @method flatten([limit] = Infinity)
1698 * @returns Array
1699 * @short Returns a flattened, one-dimensional copy of the array.
1700 * @extra You can optionally specify a [limit], which will only flatten that depth.
1701 * @example
1702 *
1703 * [[1], 2, [3]].flatten() -> [1,2,3]
1704 * [['a'],[],'b','c'].flatten() -> ['a','b','c']
1705 *
1706 ***/
1707 'flatten': function(limit) {
1708 return arrayFlatten(this, limit);
1709 },
1710
1711 /***
1712 * @method union([a1], [a2], ...)
1713 * @returns Array
1714 * @short Returns an array containing all elements in all arrays with duplic ates removed.
1715 * @extra This method will also correctly operate on arrays of objects.
1716 * @example
1717 *
1718 * [1,3,5].union([5,7,9]) -> [1,3,5,7,9]
1719 * ['a','b'].union(['b','c']) -> ['a','b','c']
1720 *
1721 ***/
1722 'union': function() {
1723 return arrayUnique(this.concat(flatArguments(arguments)));
1724 },
1725
1726 /***
1727 * @method intersect([a1], [a2], ...)
1728 * @returns Array
1729 * @short Returns an array containing the elements all arrays have in common .
1730 * @extra This method will also correctly operate on arrays of objects.
1731 * @example
1732 *
1733 * [1,3,5].intersect([5,7,9]) -> [5]
1734 * ['a','b'].intersect('b','c') -> ['b']
1735 *
1736 ***/
1737 'intersect': function() {
1738 return arrayIntersect(this, flatArguments(arguments), false);
1739 },
1740
1741 /***
1742 * @method subtract([a1], [a2], ...)
1743 * @returns Array
1744 * @short Subtracts from the array all elements in [a1], [a2], etc.
1745 * @extra This method will also correctly operate on arrays of objects.
1746 * @example
1747 *
1748 * [1,3,5].subtract([5,7,9]) -> [1,3]
1749 * [1,3,5].subtract([3],[5]) -> [1]
1750 * ['a','b'].subtract('b','c') -> ['a']
1751 *
1752 ***/
1753 'subtract': function(a) {
1754 return arrayIntersect(this, flatArguments(arguments), true);
1755 },
1756
1757 /***
1758 * @method at(<index>, [loop] = true)
1759 * @returns Mixed
1760 * @short Gets the element(s) at a given index.
1761 * @extra When [loop] is true, overshooting the end of the array (or the beg inning) will begin counting from the other end. As an alternate syntax, passing multiple indexes will get the elements at those indexes.
1762 * @example
1763 *
1764 * [1,2,3].at(0) -> 1
1765 * [1,2,3].at(2) -> 3
1766 * [1,2,3].at(4) -> 2
1767 * [1,2,3].at(4, false) -> null
1768 * [1,2,3].at(-1) -> 3
1769 * [1,2,3].at(0,1) -> [1,2]
1770 *
1771 ***/
1772 'at': function() {
1773 return getEntriesForIndexes(this, arguments);
1774 },
1775
1776 /***
1777 * @method first([num] = 1)
1778 * @returns Mixed
1779 * @short Returns the first element(s) in the array.
1780 * @extra When <num> is passed, returns the first <num> elements in the arra y.
1781 * @example
1782 *
1783 * [1,2,3].first() -> 1
1784 * [1,2,3].first(2) -> [1,2]
1785 *
1786 ***/
1787 'first': function(num) {
1788 if(isUndefined(num)) return this[0];
1789 if(num < 0) num = 0;
1790 return this.slice(0, num);
1791 },
1792
1793 /***
1794 * @method last([num] = 1)
1795 * @returns Mixed
1796 * @short Returns the last element(s) in the array.
1797 * @extra When <num> is passed, returns the last <num> elements in the array .
1798 * @example
1799 *
1800 * [1,2,3].last() -> 3
1801 * [1,2,3].last(2) -> [2,3]
1802 *
1803 ***/
1804 'last': function(num) {
1805 if(isUndefined(num)) return this[this.length - 1];
1806 var start = this.length - num < 0 ? 0 : this.length - num;
1807 return this.slice(start);
1808 },
1809
1810 /***
1811 * @method from(<index>)
1812 * @returns Array
1813 * @short Returns a slice of the array from <index>.
1814 * @example
1815 *
1816 * [1,2,3].from(1) -> [2,3]
1817 * [1,2,3].from(2) -> [3]
1818 *
1819 ***/
1820 'from': function(num) {
1821 return this.slice(num);
1822 },
1823
1824 /***
1825 * @method to(<index>)
1826 * @returns Array
1827 * @short Returns a slice of the array up to <index>.
1828 * @example
1829 *
1830 * [1,2,3].to(1) -> [1]
1831 * [1,2,3].to(2) -> [1,2]
1832 *
1833 ***/
1834 'to': function(num) {
1835 if(isUndefined(num)) num = this.length;
1836 return this.slice(0, num);
1837 },
1838
1839 /***
1840 * @method min([map], [all] = false)
1841 * @returns Mixed
1842 * @short Returns the element in the array with the lowest value.
1843 * @extra [map] may be a function mapping the value to be checked or a strin g acting as a shortcut. If [all] is true, will return all min values in an array .
1844 * @example
1845 *
1846 * [1,2,3].min() -> 1
1847 * ['fee','fo','fum'].min('length') -> 'fo'
1848 * ['fee','fo','fum'].min('length', true) -> ['fo']
1849 + ['fee','fo','fum'].min(function(n) {
1850 * return n.length;
1851 * }); -> ['fo']
1852 + [{a:3,a:2}].min(function(n) {
1853 * return n['a'];
1854 * }); -> [{a:2}]
1855 *
1856 ***/
1857 'min': function(map, all) {
1858 return getMinOrMax(this, map, 'min', all);
1859 },
1860
1861 /***
1862 * @method max([map], [all] = false)
1863 * @returns Mixed
1864 * @short Returns the element in the array with the greatest value.
1865 * @extra [map] may be a function mapping the value to be checked or a strin g acting as a shortcut. If [all] is true, will return all max values in an array .
1866 * @example
1867 *
1868 * [1,2,3].max() -> 3
1869 * ['fee','fo','fum'].max('length') -> 'fee'
1870 * ['fee','fo','fum'].max('length', true) -> ['fee']
1871 + [{a:3,a:2}].max(function(n) {
1872 * return n['a'];
1873 * }); -> {a:3}
1874 *
1875 ***/
1876 'max': function(map, all) {
1877 return getMinOrMax(this, map, 'max', all);
1878 },
1879
1880 /***
1881 * @method least([map])
1882 * @returns Array
1883 * @short Returns the elements in the array with the least commonly occuring value.
1884 * @extra [map] may be a function mapping the value to be checked or a strin g acting as a shortcut.
1885 * @example
1886 *
1887 * [3,2,2].least() -> [3]
1888 * ['fe','fo','fum'].least('length') -> ['fum']
1889 + [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].least(fun ction(n) {
1890 * return n.age;
1891 * }); -> [{age:35,name:'ken'}]
1892 *
1893 ***/
1894 'least': function(map, all) {
1895 return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'min', all);
1896 },
1897
1898 /***
1899 * @method most([map])
1900 * @returns Array
1901 * @short Returns the elements in the array with the most commonly occuring value.
1902 * @extra [map] may be a function mapping the value to be checked or a strin g acting as a shortcut.
1903 * @example
1904 *
1905 * [3,2,2].most() -> [2]
1906 * ['fe','fo','fum'].most('length') -> ['fe','fo']
1907 + [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].most(func tion(n) {
1908 * return n.age;
1909 * }); -> [{age:12,name:'bob'},{age:12,name:' ted'}]
1910 *
1911 ***/
1912 'most': function(map, all) {
1913 return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'max', all);
1914 },
1915
1916 /***
1917 * @method sum([map])
1918 * @returns Number
1919 * @short Sums all values in the array.
1920 * @extra [map] may be a function mapping the value to be summed or a string acting as a shortcut.
1921 * @example
1922 *
1923 * [1,2,2].sum() -> 5
1924 + [{age:35},{age:12},{age:12}].sum(function(n) {
1925 * return n.age;
1926 * }); -> 59
1927 * [{age:35},{age:12},{age:12}].sum('age') -> 59
1928 *
1929 ***/
1930 'sum': function(map) {
1931 var arr = map ? this.map(map) : this;
1932 return arr.length > 0 ? arr.reduce(function(a,b) { return a + b; }) : 0;
1933 },
1934
1935 /***
1936 * @method average([map])
1937 * @returns Number
1938 * @short Gets the mean average for all values in the array.
1939 * @extra [map] may be a function mapping the value to be averaged or a stri ng acting as a shortcut.
1940 * @example
1941 *
1942 * [1,2,3].average() -> 2
1943 + [{age:35},{age:11},{age:11}].average(function(n) {
1944 * return n.age;
1945 * }); -> 19
1946 * [{age:35},{age:11},{age:11}].average('age') -> 19
1947 *
1948 ***/
1949 'average': function(map) {
1950 var arr = map ? this.map(map) : this;
1951 return arr.length > 0 ? arr.sum() / arr.length : 0;
1952 },
1953
1954 /***
1955 * @method inGroups(<num>, [padding])
1956 * @returns Array
1957 * @short Groups the array into <num> arrays.
1958 * @extra [padding] specifies a value with which to pad the last array so th at they are all equal length.
1959 * @example
1960 *
1961 * [1,2,3,4,5,6,7].inGroups(3) -> [ [1,2,3], [4,5,6], [7] ]
1962 * [1,2,3,4,5,6,7].inGroups(3, 'none') -> [ [1,2,3], [4,5,6], [7,'none','n one'] ]
1963 *
1964 ***/
1965 'inGroups': function(num, padding) {
1966 var pad = arguments.length > 1;
1967 var arr = this;
1968 var result = [];
1969 var divisor = ceil(this.length / num);
1970 simpleRepeat(num, function(i) {
1971 var index = i * divisor;
1972 var group = arr.slice(index, index + divisor);
1973 if(pad && group.length < divisor) {
1974 simpleRepeat(divisor - group.length, function() {
1975 group = group.add(padding);
1976 });
1977 }
1978 result.push(group);
1979 });
1980 return result;
1981 },
1982
1983 /***
1984 * @method inGroupsOf(<num>, [padding] = null)
1985 * @returns Array
1986 * @short Groups the array into arrays of <num> elements each.
1987 * @extra [padding] specifies a value with which to pad the last array so th at they are all equal length.
1988 * @example
1989 *
1990 * [1,2,3,4,5,6,7].inGroupsOf(4) -> [ [1,2,3,4], [5,6,7] ]
1991 * [1,2,3,4,5,6,7].inGroupsOf(4, 'none') -> [ [1,2,3,4], [5,6,7,'none'] ]
1992 *
1993 ***/
1994 'inGroupsOf': function(num, padding) {
1995 var result = [], len = this.length, arr = this, group;
1996 if(len === 0 || num === 0) return arr;
1997 if(isUndefined(num)) num = 1;
1998 if(isUndefined(padding)) padding = null;
1999 simpleRepeat(ceil(len / num), function(i) {
2000 group = arr.slice(num * i, num * i + num);
2001 while(group.length < num) {
2002 group.push(padding);
2003 }
2004 result.push(group);
2005 });
2006 return result;
2007 },
2008
2009 /***
2010 * @method isEmpty()
2011 * @returns Boolean
2012 * @short Returns true if the array is empty.
2013 * @extra This is true if the array has a length of zero, or contains only % undefined%, %null%, or %NaN%.
2014 * @example
2015 *
2016 * [].isEmpty() -> true
2017 * [null,undefined].isEmpty() -> true
2018 *
2019 ***/
2020 'isEmpty': function() {
2021 return this.compact().length == 0;
2022 },
2023
2024 /***
2025 * @method sortBy(<map>, [desc] = false)
2026 * @returns Array
2027 * @short Sorts the array by <map>.
2028 * @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 ord er. When the field being sorted on is a string, the resulting order will be dete rmined by an internal collation algorithm that is optimized for major Western la nguages, but can be customized. For more information see @array_sorting.
2029 * @example
2030 *
2031 * ['world','a','new'].sortBy('length') -> ['a','new','world']
2032 * ['world','a','new'].sortBy('length', true) -> ['world','new','a']
2033 + [{age:72},{age:13},{age:18}].sortBy(function(n) {
2034 * return n.age;
2035 * }); -> [{age:13},{age:18},{age:7 2}]
2036 *
2037 ***/
2038 'sortBy': function(map, desc) {
2039 var arr = this.clone();
2040 arr.sort(function(a, b) {
2041 var aProperty, bProperty, comp;
2042 aProperty = transformArgument(a, map, arr, [a]);
2043 bProperty = transformArgument(b, map, arr, [b]);
2044 if(isString(aProperty) && isString(bProperty)) {
2045 comp = collateStrings(aProperty, bProperty);
2046 } else if(aProperty < bProperty) {
2047 comp = -1;
2048 } else if(aProperty > bProperty) {
2049 comp = 1;
2050 } else {
2051 comp = 0;
2052 }
2053 return comp * (desc ? -1 : 1);
2054 });
2055 return arr;
2056 },
2057
2058 /***
2059 * @method randomize()
2060 * @returns Array
2061 * @short Returns a copy of the array with the elements randomized.
2062 * @extra Uses Fisher-Yates algorithm.
2063 * @example
2064 *
2065 * [1,2,3,4].randomize() -> [?,?,?,?]
2066 *
2067 ***/
2068 'randomize': function() {
2069 var arr = this.concat(), i = arr.length, j, x;
2070 while(i) {
2071 j = (math.random() * i) | 0;
2072 x = arr[--i];
2073 arr[i] = arr[j];
2074 arr[j] = x;
2075 }
2076 return arr;
2077 },
2078
2079 /***
2080 * @method zip([arr1], [arr2], ...)
2081 * @returns Array
2082 * @short Merges multiple arrays together.
2083 * @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 h ave more elements than the original array, they will be discarded. If they have fewer elements, the missing elements will filled with %null%.
2084 * @example
2085 *
2086 * [1,2,3].zip([4,5,6]) -> [[1,2], [ 3,4], [5,6]]
2087 * ['Martin','John'].zip(['Luther','F.'], ['King','Kennedy']) -> [['Martin ','Luther','King'], ['John','F.','Kennedy']]
2088 *
2089 ***/
2090 'zip': function() {
2091 var args = multiArgs(arguments);
2092 return this.map(function(el, i) {
2093 return [el].concat(args.map(function(k) {
2094 return (i in k) ? k[i] : null;
2095 }));
2096 });
2097 },
2098
2099 /***
2100 * @method sample([num])
2101 * @returns Mixed
2102 * @short Returns a random element from the array.
2103 * @extra If [num] is passed, will return [num] samples from the array.
2104 * @example
2105 *
2106 * [1,2,3,4,5].sample() -> // Random element
2107 * [1,2,3,4,5].sample(3) -> // Array of 3 random elements
2108 *
2109 ***/
2110 'sample': function(num) {
2111 var arr = this.randomize();
2112 return arguments.length > 0 ? arr.slice(0, num) : arr[0];
2113 },
2114
2115 /***
2116 * @method each(<fn>, [index] = 0, [loop] = false)
2117 * @returns Array
2118 * @short Runs <fn> against each element in the array. Enhanced version of % Array#forEach%.
2119 * @extra Parameters passed to <fn> are identical to %forEach%, ie. the firs t parameter is the current element, second parameter is the current index, and t hird 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.
2120 * @example
2121 *
2122 * [1,2,3,4].each(function(n) {
2123 * // Called 4 times: 1, 2, 3, 4
2124 * });
2125 * [1,2,3,4].each(function(n) {
2126 * // Called 4 times: 3, 4, 1, 2
2127 * }, 2, true);
2128 *
2129 ***/
2130 'each': function(fn, index, loop) {
2131 arrayEach(this, fn, index, loop);
2132 return this;
2133 },
2134
2135 /***
2136 * @method add(<el>, [index])
2137 * @returns Array
2138 * @short Adds <el> to the array.
2139 * @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 i t 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.
2140 * @example
2141 *
2142 * [1,2,3,4].add(5) -> [1,2,3,4,5]
2143 * [1,2,3,4].add([5,6,7]) -> [1,2,3,4,5,6,7]
2144 * [1,2,3,4].insert(8, 1) -> [1,8,2,3,4]
2145 *
2146 ***/
2147 'add': function(el, index) {
2148 if(!isNumber(number(index)) || isNaN(index)) index = this.length;
2149 array.prototype.splice.apply(this, [index, 0].concat(el));
2150 return this;
2151 },
2152
2153 /***
2154 * @method remove([f1], [f2], ...)
2155 * @returns Array
2156 * @short Removes any element in the array that matches [f1], [f2], etc.
2157 * @extra Will match a string, number, array, object, or alternately test ag ainst a function or regex. This method will change the array! Use %exclude% for a non-destructive alias. This method implements @array_matching.
2158 * @example
2159 *
2160 * [1,2,3].remove(3) -> [1,2]
2161 * ['a','b','c'].remove(/b/) -> ['a','c']
2162 + [{a:1},{b:2}].remove(function(n) {
2163 * return n['a'] == 1;
2164 * }); -> [{b:2}]
2165 *
2166 ***/
2167 'remove': function() {
2168 var arr = this;
2169 multiArgs(arguments, function(f) {
2170 var i = 0, matcher = getMatcher(f);
2171 while(i < arr.length) {
2172 if(matcher(arr[i], i, arr)) {
2173 arr.splice(i, 1);
2174 } else {
2175 i++;
2176 }
2177 }
2178 });
2179 return arr;
2180 },
2181
2182 /***
2183 * @method compact([all] = false)
2184 * @returns Array
2185 * @short Removes all instances of %undefined%, %null%, and %NaN% from the a rray.
2186 * @extra If [all] is %true%, all "falsy" elements will be removed. This inc ludes empty strings, 0, and false.
2187 * @example
2188 *
2189 * [1,null,2,undefined,3].compact() -> [1,2,3]
2190 * [1,'',2,false,3].compact() -> [1,'',2,false,3]
2191 * [1,'',2,false,3].compact(true) -> [1,2,3]
2192 *
2193 ***/
2194 'compact': function(all) {
2195 var result = [];
2196 arrayEach(this, function(el, i) {
2197 if(isArray(el)) {
2198 result.push(el.compact());
2199 } else if(all && el) {
2200 result.push(el);
2201 } else if(!all && el != null && el.valueOf() === el.valueOf()) {
2202 result.push(el);
2203 }
2204 });
2205 return result;
2206 },
2207
2208 /***
2209 * @method groupBy(<map>, [fn])
2210 * @returns Object
2211 * @short Groups the array by <map>.
2212 * @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.
2213 * @example
2214 *
2215 * ['fee','fi','fum'].groupBy('length') -> { 2: ['fi'], 3: ['fee','fum'] }
2216 + [{age:35,name:'ken'},{age:15,name:'bob'}].groupBy(function(n) {
2217 * return n.age;
2218 * }); -> { 35: [{age:35,name:'ken'}], 15 : [{age:15,name:'bob'}] }
2219 *
2220 ***/
2221 'groupBy': function(map, fn) {
2222 var arr = this, result = {}, key;
2223 arrayEach(arr, function(el, index) {
2224 key = transformArgument(el, map, arr, [el, index, arr]);
2225 if(!result[key]) result[key] = [];
2226 result[key].push(el);
2227 });
2228 if(fn) {
2229 iterateOverObject(result, fn);
2230 }
2231 return result;
2232 },
2233
2234 /***
2235 * @method none(<f>)
2236 * @returns Boolean
2237 * @short Returns true if none of the elements in the array match <f>.
2238 * @extra <f> will match a string, number, array, object, or alternately tes t against a function or regex. This method implements @array_matching.
2239 * @example
2240 *
2241 * [1,2,3].none(5) -> true
2242 * ['a','b','c'].none(/b/) -> false
2243 + [{a:1},{b:2}].none(function(n) {
2244 * return n['a'] > 1;
2245 * }); -> true
2246 *
2247 ***/
2248 'none': function() {
2249 return !this.any.apply(this, arguments);
2250 }
2251
2252
2253 });
2254
2255
2256 // Aliases
2257
2258 extend(array, true, true, {
2259
2260 /***
2261 * @method all()
2262 * @alias every
2263 *
2264 ***/
2265 'all': array.prototype.every,
2266
2267 /*** @method any()
2268 * @alias some
2269 *
2270 ***/
2271 'any': array.prototype.some,
2272
2273 /***
2274 * @method insert()
2275 * @alias add
2276 *
2277 ***/
2278 'insert': array.prototype.add
2279
2280 });
2281
2282
2283 /***
2284 * Object module
2285 * Enumerable methods on objects
2286 *
2287 ***/
2288
2289 function keysWithObjectCoercion(obj) {
2290 return object.keys(coercePrimitiveToObject(obj));
2291 }
2292
2293 /***
2294 * @method [enumerable](<obj>)
2295 * @returns Boolean
2296 * @short Enumerable methods in the Array package are also available to the Ob ject class. They will perform their normal operations for every property in <obj >.
2297 * @extra In cases where a callback is used, instead of %element, index%, the callback will instead be passed %key, value%. Enumerable methods are also availa ble to extended objects as instance methods.
2298 *
2299 * @set
2300 * each
2301 * map
2302 * any
2303 * all
2304 * none
2305 * count
2306 * find
2307 * findAll
2308 * reduce
2309 * isEmpty
2310 * sum
2311 * average
2312 * min
2313 * max
2314 * least
2315 * most
2316 *
2317 * @example
2318 *
2319 * Object.any({foo:'bar'}, 'bar') -> true
2320 * Object.extended({foo:'bar'}).any('bar') -> true
2321 * Object.isEmpty({}) -> true
2322 + Object.map({ fred: { age: 52 } }, 'age'); -> { fred: 52 }
2323 *
2324 ***/
2325
2326 function buildEnumerableMethods(names, mapping) {
2327 extendSimilar(object, false, true, names, function(methods, name) {
2328 methods[name] = function(obj, arg1, arg2) {
2329 var result, coerced = keysWithObjectCoercion(obj), matcher;
2330 if(!mapping) {
2331 matcher = getMatcher(arg1, true);
2332 }
2333 result = array.prototype[name].call(coerced, function(key) {
2334 var value = obj[key];
2335 if(mapping) {
2336 return transformArgument(value, arg1, obj, [key, value, obj]);
2337 } else {
2338 return matcher(value, key, obj);
2339 }
2340 }, arg2);
2341 if(isArray(result)) {
2342 // The method has returned an array of keys so use this array
2343 // to build up the resulting object in the form we want it in.
2344 result = result.reduce(function(o, key, i) {
2345 o[key] = obj[key];
2346 return o;
2347 }, {});
2348 }
2349 return result;
2350 };
2351 });
2352 buildObjectInstanceMethods(names, Hash);
2353 }
2354
2355 function exportSortAlgorithm() {
2356 array[AlphanumericSort] = collateStrings;
2357 }
2358
2359 extend(object, false, true, {
2360
2361 'map': function(obj, map) {
2362 var result = {}, key, value;
2363 for(key in obj) {
2364 if(!hasOwnProperty(obj, key)) continue;
2365 value = obj[key];
2366 result[key] = transformArgument(value, map, obj, [key, value, obj]);
2367 }
2368 return result;
2369 },
2370
2371 'reduce': function(obj) {
2372 var values = keysWithObjectCoercion(obj).map(function(key) {
2373 return obj[key];
2374 });
2375 return values.reduce.apply(values, multiArgs(arguments, null, 1));
2376 },
2377
2378 'each': function(obj, fn) {
2379 checkCallback(fn);
2380 iterateOverObject(obj, fn);
2381 return obj;
2382 },
2383
2384 /***
2385 * @method size(<obj>)
2386 * @returns Number
2387 * @short Returns the number of properties in <obj>.
2388 * @extra %size% is available as an instance method on extended objects.
2389 * @example
2390 *
2391 * Object.size({ foo: 'bar' }) -> 1
2392 *
2393 ***/
2394 'size': function (obj) {
2395 return keysWithObjectCoercion(obj).length;
2396 }
2397
2398 });
2399
2400 var EnumerableFindingMethods = 'any,all,none,count,find,findAll,isEmpty'.split (',');
2401 var EnumerableMappingMethods = 'sum,average,min,max,least,most'.split(',');
2402 var EnumerableOtherMethods = 'map,reduce,size'.split(',');
2403 var EnumerableMethods = EnumerableFindingMethods.concat(EnumerableMappi ngMethods).concat(EnumerableOtherMethods);
2404
2405 buildEnhancements();
2406 buildAlphanumericSort();
2407 buildEnumerableMethods(EnumerableFindingMethods);
2408 buildEnumerableMethods(EnumerableMappingMethods, true);
2409 buildObjectInstanceMethods(EnumerableOtherMethods, Hash);
2410 exportSortAlgorithm();
2411
2412
2413 /***
2414 * @package Date
2415 * @dependency core
2416 * @description Date parsing and formatting, relative formats like "1 minute a go", Number methods like "daysAgo", localization support with default English lo cale definition.
2417 *
2418 ***/
2419
2420 var English;
2421 var CurrentLocalization;
2422
2423 var TimeFormat = ['ampm','hour','minute','second','ampm','utc','offset_sign',' offset_hours','offset_minutes','ampm']
2424 var DecimalReg = '(?:[,.]\\d+)?';
2425 var HoursReg = '\\d{1,2}' + DecimalReg;
2426 var SixtyReg = '[0-5]\\d' + DecimalReg;
2427 var RequiredTime = '({t})?\\s*('+HoursReg+')(?:{h}('+SixtyReg+')?{m}(?::?('+Si xtyReg+'){s})?\\s*(?:({t})|(Z)|(?:([+-])(\\d{2,2})(?::?(\\d{2,2}))?)?)?|\\s*({t} ))';
2428
2429 var KanjiDigits = '〇一二三四五六七八九十百千万';
2430 var AsianDigitMap = {};
2431 var AsianDigitReg;
2432
2433 var DateArgumentUnits;
2434 var DateUnitsReversed;
2435 var CoreDateFormats = [];
2436 var CompiledOutputFormats = {};
2437
2438 var DateFormatTokens = {
2439
2440 'yyyy': function(d) {
2441 return callDateGet(d, 'FullYear');
2442 },
2443
2444 'yy': function(d) {
2445 return callDateGet(d, 'FullYear') % 100;
2446 },
2447
2448 'ord': function(d) {
2449 var date = callDateGet(d, 'Date');
2450 return date + getOrdinalizedSuffix(date);
2451 },
2452
2453 'tz': function(d) {
2454 return d.getUTCOffset();
2455 },
2456
2457 'isotz': function(d) {
2458 return d.getUTCOffset(true);
2459 },
2460
2461 'Z': function(d) {
2462 return d.getUTCOffset();
2463 },
2464
2465 'ZZ': function(d) {
2466 return d.getUTCOffset().replace(/(\d{2})$/, ':$1');
2467 }
2468
2469 };
2470
2471 var DateUnits = [
2472 {
2473 name: 'year',
2474 method: 'FullYear',
2475 ambiguous: true,
2476 multiplier: function(d) {
2477 var adjust = d ? (d.isLeapYear() ? 1 : 0) : 0.25;
2478 return (365 + adjust) * 24 * 60 * 60 * 1000;
2479 }
2480 },
2481 {
2482 name: 'month',
2483 error: 0.919, // Feb 1-28 over 1 month
2484 method: 'Month',
2485 ambiguous: true,
2486 multiplier: function(d, ms) {
2487 var days = 30.4375, inMonth;
2488 if(d) {
2489 inMonth = d.daysInMonth();
2490 if(ms <= inMonth.days()) {
2491 days = inMonth;
2492 }
2493 }
2494 return days * 24 * 60 * 60 * 1000;
2495 }
2496 },
2497 {
2498 name: 'week',
2499 method: 'ISOWeek',
2500 multiplier: function() {
2501 return 7 * 24 * 60 * 60 * 1000;
2502 }
2503 },
2504 {
2505 name: 'day',
2506 error: 0.958, // DST traversal over 1 day
2507 method: 'Date',
2508 ambiguous: true,
2509 multiplier: function() {
2510 return 24 * 60 * 60 * 1000;
2511 }
2512 },
2513 {
2514 name: 'hour',
2515 method: 'Hours',
2516 multiplier: function() {
2517 return 60 * 60 * 1000;
2518 }
2519 },
2520 {
2521 name: 'minute',
2522 method: 'Minutes',
2523 multiplier: function() {
2524 return 60 * 1000;
2525 }
2526 },
2527 {
2528 name: 'second',
2529 method: 'Seconds',
2530 multiplier: function() {
2531 return 1000;
2532 }
2533 },
2534 {
2535 name: 'millisecond',
2536 method: 'Milliseconds',
2537 multiplier: function() {
2538 return 1;
2539 }
2540 }
2541 ];
2542
2543
2544
2545
2546 // Date Localization
2547
2548 var Localizations = {};
2549
2550 // Localization object
2551
2552 function Localization(l) {
2553 simpleMerge(this, l);
2554 this.compiledFormats = CoreDateFormats.concat();
2555 }
2556
2557 Localization.prototype = {
2558
2559 getMonth: function(n) {
2560 if(isNumber(n)) {
2561 return n - 1;
2562 } else {
2563 return this['months'].indexOf(n) % 12;
2564 }
2565 },
2566
2567 getWeekday: function(n) {
2568 return this['weekdays'].indexOf(n) % 7;
2569 },
2570
2571 getNumber: function(n) {
2572 var i;
2573 if(isNumber(n)) {
2574 return n;
2575 } else if(n && (i = this['numbers'].indexOf(n)) !== -1) {
2576 return (i + 1) % 10;
2577 } else {
2578 return 1;
2579 }
2580 },
2581
2582 getNumericDate: function(n) {
2583 var self = this;
2584 return n.replace(regexp(this['num'], 'g'), function(d) {
2585 var num = self.getNumber(d);
2586 return num || '';
2587 });
2588 },
2589
2590 getUnitIndex: function(n) {
2591 return this['units'].indexOf(n) % 8;
2592 },
2593
2594 getRelativeFormat: function(adu) {
2595 return this.convertAdjustedToFormat(adu, adu[2] > 0 ? 'future' : 'past');
2596 },
2597
2598 getDuration: function(ms) {
2599 return this.convertAdjustedToFormat(getAdjustedUnit(ms), 'duration');
2600 },
2601
2602 hasVariant: function(code) {
2603 code = code || this.code;
2604 return code === 'en' || code === 'en-US' ? true : this['variant'];
2605 },
2606
2607 matchAM: function(str) {
2608 return str === this['ampm'][0];
2609 },
2610
2611 matchPM: function(str) {
2612 return str && str === this['ampm'][1];
2613 },
2614
2615 convertAdjustedToFormat: function(adu, mode) {
2616 var sign, unit, mult,
2617 num = adu[0],
2618 u = adu[1],
2619 ms = adu[2],
2620 format = this[mode] || this['relative'];
2621 if(isFunction(format)) {
2622 return format.call(this, num, u, ms, mode);
2623 }
2624 mult = this['plural'] && num > 1 ? 1 : 0;
2625 unit = this['units'][mult * 8 + u] || this['units'][u];
2626 if(this['capitalizeUnit']) unit = simpleCapitalize(unit);
2627 sign = this['modifiers'].filter(function(m) { return m.name == 'sign' && m .value == (ms > 0 ? 1 : -1); })[0];
2628 return format.replace(/\{(.*?)\}/g, function(full, match) {
2629 switch(match) {
2630 case 'num': return num;
2631 case 'unit': return unit;
2632 case 'sign': return sign.src;
2633 }
2634 });
2635 },
2636
2637 getFormats: function() {
2638 return this.cachedFormat ? [this.cachedFormat].concat(this.compiledFormats ) : this.compiledFormats;
2639 },
2640
2641 addFormat: function(src, allowsTime, match, variant, iso) {
2642 var to = match || [], loc = this, time, timeMarkers, lastIsNumeral;
2643
2644 src = src.replace(/\s+/g, '[,. ]*');
2645 src = src.replace(/\{([^,]+?)\}/g, function(all, k) {
2646 var value, arr, result,
2647 opt = k.match(/\?$/),
2648 nc = k.match(/^(\d+)\??$/),
2649 slice = k.match(/(\d)(?:-(\d))?/),
2650 key = k.replace(/[^a-z]+$/, '');
2651 if(nc) {
2652 value = loc['tokens'][nc[1]];
2653 } else if(loc[key]) {
2654 value = loc[key];
2655 } else if(loc[key + 's']) {
2656 value = loc[key + 's'];
2657 if(slice) {
2658 // Can't use filter here as Prototype hijacks the method and doesn't
2659 // pass an index, so use a simple loop instead!
2660 arr = [];
2661 value.forEach(function(m, i) {
2662 var mod = i % (loc['units'] ? 8 : value.length);
2663 if(mod >= slice[1] && mod <= (slice[2] || slice[1])) {
2664 arr.push(m);
2665 }
2666 });
2667 value = arr;
2668 }
2669 value = arrayToAlternates(value);
2670 }
2671 if(nc) {
2672 result = '(?:' + value + ')';
2673 } else {
2674 if(!match) {
2675 to.push(key);
2676 }
2677 result = '(' + value + ')';
2678 }
2679 if(opt) {
2680 result += '?';
2681 }
2682 return result;
2683 });
2684 if(allowsTime) {
2685 time = prepareTime(RequiredTime, loc, iso);
2686 timeMarkers = ['t','[\\s\\u3000]'].concat(loc['timeMarker']);
2687 lastIsNumeral = src.match(/\\d\{\d,\d\}\)+\??$/);
2688 addDateInputFormat(loc, '(?:' + time + ')[,\\s\\u3000]+?' + src, TimeFor mat.concat(to), variant);
2689 addDateInputFormat(loc, src + '(?:[,\\s]*(?:' + timeMarkers.join('|') + (lastIsNumeral ? '+' : '*') +')' + time + ')?', to.concat(TimeFormat), variant);
2690 } else {
2691 addDateInputFormat(loc, src, to, variant);
2692 }
2693 }
2694
2695 };
2696
2697
2698 // Localization helpers
2699
2700 function getLocalization(localeCode, fallback) {
2701 var loc;
2702 if(!isString(localeCode)) localeCode = '';
2703 loc = Localizations[localeCode] || Localizations[localeCode.slice(0,2)];
2704 if(fallback === false && !loc) {
2705 throw new TypeError('Invalid locale.');
2706 }
2707 return loc || CurrentLocalization;
2708 }
2709
2710 function setLocalization(localeCode, set) {
2711 var loc, canAbbreviate;
2712
2713 function initializeField(name) {
2714 var val = loc[name];
2715 if(isString(val)) {
2716 loc[name] = val.split(',');
2717 } else if(!val) {
2718 loc[name] = [];
2719 }
2720 }
2721
2722 function eachAlternate(str, fn) {
2723 str = str.split('+').map(function(split) {
2724 return split.replace(/(.+):(.+)$/, function(full, base, suffixes) {
2725 return suffixes.split('|').map(function(suffix) {
2726 return base + suffix;
2727 }).join('|');
2728 });
2729 }).join('|');
2730 return str.split('|').forEach(fn);
2731 }
2732
2733 function setArray(name, abbreviate, multiple) {
2734 var arr = [];
2735 loc[name].forEach(function(full, i) {
2736 if(abbreviate) {
2737 full += '+' + full.slice(0,3);
2738 }
2739 eachAlternate(full, function(day, j) {
2740 arr[j * multiple + i] = day.toLowerCase();
2741 });
2742 });
2743 loc[name] = arr;
2744 }
2745
2746 function getDigit(start, stop, allowNumbers) {
2747 var str = '\\d{' + start + ',' + stop + '}';
2748 if(allowNumbers) str += '|(?:' + arrayToAlternates(loc['numbers']) + ')+';
2749 return str;
2750 }
2751
2752 function getNum() {
2753 var arr = ['-?\\d+'].concat(loc['articles']);
2754 if(loc['numbers']) arr = arr.concat(loc['numbers']);
2755 return arrayToAlternates(arr);
2756 }
2757
2758 function setDefault(name, value) {
2759 loc[name] = loc[name] || value;
2760 }
2761
2762 function setModifiers() {
2763 var arr = [];
2764 loc.modifiersByName = {};
2765 loc['modifiers'].push({ 'name': 'day', 'src': 'yesterday', 'value': -1 });
2766 loc['modifiers'].push({ 'name': 'day', 'src': 'today', 'value': 0 });
2767 loc['modifiers'].push({ 'name': 'day', 'src': 'tomorrow', 'value': 1 });
2768 loc['modifiers'].forEach(function(modifier) {
2769 var name = modifier.name;
2770 eachAlternate(modifier.src, function(t) {
2771 var locEntry = loc[name];
2772 loc.modifiersByName[t] = modifier;
2773 arr.push({ name: name, src: t, value: modifier.value });
2774 loc[name] = locEntry ? locEntry + '|' + t : t;
2775 });
2776 });
2777 loc['day'] += '|' + arrayToAlternates(loc['weekdays']);
2778 loc['modifiers'] = arr;
2779 }
2780
2781 // Initialize the locale
2782 loc = new Localization(set);
2783 initializeField('modifiers');
2784 'months,weekdays,units,numbers,articles,tokens,timeMarker,ampm,timeSuffixes, dateParse,timeParse'.split(',').forEach(initializeField);
2785
2786 canAbbreviate = !loc['monthSuffix'];
2787
2788 setArray('months', canAbbreviate, 12);
2789 setArray('weekdays', canAbbreviate, 7);
2790 setArray('units', false, 8);
2791 setArray('numbers', false, 10);
2792
2793 setDefault('code', localeCode);
2794 setDefault('date', getDigit(1,2, loc['digitDate']));
2795 setDefault('year', "'\\d{2}|" + getDigit(4,4));
2796 setDefault('num', getNum());
2797
2798 setModifiers();
2799
2800 if(loc['monthSuffix']) {
2801 loc['month'] = getDigit(1,2);
2802 loc['months'] = '1,2,3,4,5,6,7,8,9,10,11,12'.split(',').map(function(n) { return n + loc['monthSuffix']; });
2803 }
2804 loc['full_month'] = getDigit(1,2) + '|' + arrayToAlternates(loc['months']);
2805
2806 // The order of these formats is very important. Order is reversed so format s that come
2807 // later will take precedence over formats that come before. This generally means that
2808 // more specific formats should come later, however, the {year} format shoul d come before
2809 // {day}, as 2011 needs to be parsed as a year (2011) and not date (20) + ho urs (11)
2810
2811 // If the locale has time suffixes then add a time only format for that loca le
2812 // that is separate from the core English-based one.
2813 if(loc['timeSuffixes'].length > 0) {
2814 loc.addFormat(prepareTime(RequiredTime, loc), false, TimeFormat)
2815 }
2816
2817 loc.addFormat('{day}', true);
2818 loc.addFormat('{month}' + (loc['monthSuffix'] || ''));
2819 loc.addFormat('{year}' + (loc['yearSuffix'] || ''));
2820
2821 loc['timeParse'].forEach(function(src) {
2822 loc.addFormat(src, true);
2823 });
2824
2825 loc['dateParse'].forEach(function(src) {
2826 loc.addFormat(src);
2827 });
2828
2829 return Localizations[localeCode] = loc;
2830 }
2831
2832
2833 // General helpers
2834
2835 function addDateInputFormat(locale, format, match, variant) {
2836 locale.compiledFormats.unshift({
2837 variant: variant,
2838 locale: locale,
2839 reg: regexp('^' + format + '$', 'i'),
2840 to: match
2841 });
2842 }
2843
2844 function simpleCapitalize(str) {
2845 return str.slice(0,1).toUpperCase() + str.slice(1);
2846 }
2847
2848 function arrayToAlternates(arr) {
2849 return arr.filter(function(el) {
2850 return !!el;
2851 }).join('|');
2852 }
2853
2854 function getNewDate() {
2855 var fn = date.SugarNewDate;
2856 return fn ? fn() : new date;
2857 }
2858
2859 // Date argument helpers
2860
2861 function collectDateArguments(args, allowDuration) {
2862 var obj;
2863 if(isObjectType(args[0])) {
2864 return args;
2865 } else if (isNumber(args[0]) && !isNumber(args[1])) {
2866 return [args[0]];
2867 } else if (isString(args[0]) && allowDuration) {
2868 return [getDateParamsFromString(args[0]), args[1]];
2869 }
2870 obj = {};
2871 DateArgumentUnits.forEach(function(u,i) {
2872 obj[u.name] = args[i];
2873 });
2874 return [obj];
2875 }
2876
2877 function getDateParamsFromString(str, num) {
2878 var match, params = {};
2879 match = str.match(/^(\d+)?\s?(\w+?)s?$/i);
2880 if(match) {
2881 if(isUndefined(num)) {
2882 num = parseInt(match[1]) || 1;
2883 }
2884 params[match[2].toLowerCase()] = num;
2885 }
2886 return params;
2887 }
2888
2889 // Date iteration helpers
2890
2891 function iterateOverDateUnits(fn, from, to) {
2892 var i, unit;
2893 if(isUndefined(to)) to = DateUnitsReversed.length;
2894 for(i = from || 0; i < to; i++) {
2895 unit = DateUnitsReversed[i];
2896 if(fn(unit.name, unit, i) === false) {
2897 break;
2898 }
2899 }
2900 }
2901
2902 // Date parsing helpers
2903
2904 function getFormatMatch(match, arr) {
2905 var obj = {}, value, num;
2906 arr.forEach(function(key, i) {
2907 value = match[i + 1];
2908 if(isUndefined(value) || value === '') return;
2909 if(key === 'year') {
2910 obj.yearAsString = value.replace(/'/, '');
2911 }
2912 num = parseFloat(value.replace(/'/, '').replace(/,/, '.'));
2913 obj[key] = !isNaN(num) ? num : value.toLowerCase();
2914 });
2915 return obj;
2916 }
2917
2918 function cleanDateInput(str) {
2919 str = str.trim().replace(/^just (?=now)|\.+$/i, '');
2920 return convertAsianDigits(str);
2921 }
2922
2923 function convertAsianDigits(str) {
2924 return str.replace(AsianDigitReg, function(full, disallowed, match) {
2925 var sum = 0, place = 1, lastWasHolder, lastHolder;
2926 if(disallowed) return full;
2927 match.split('').reverse().forEach(function(letter) {
2928 var value = AsianDigitMap[letter], holder = value > 9;
2929 if(holder) {
2930 if(lastWasHolder) sum += place;
2931 place *= value / (lastHolder || 1);
2932 lastHolder = value;
2933 } else {
2934 if(lastWasHolder === false) {
2935 place *= 10;
2936 }
2937 sum += place * value;
2938 }
2939 lastWasHolder = holder;
2940 });
2941 if(lastWasHolder) sum += place;
2942 return sum;
2943 });
2944 }
2945
2946 function getExtendedDate(f, localeCode, prefer, forceUTC) {
2947 var d, relative, baseLocalization, afterCallbacks, loc, set, unit, unitIndex , weekday, num, tmp;
2948
2949 d = getNewDate();
2950 afterCallbacks = [];
2951
2952 function afterDateSet(fn) {
2953 afterCallbacks.push(fn);
2954 }
2955
2956 function fireCallbacks() {
2957 afterCallbacks.forEach(function(fn) {
2958 fn.call();
2959 });
2960 }
2961
2962 function setWeekdayOfMonth() {
2963 var w = d.getWeekday();
2964 d.setWeekday((7 * (set['num'] - 1)) + (w > weekday ? weekday + 7 : weekday ));
2965 }
2966
2967 function setUnitEdge() {
2968 var modifier = loc.modifiersByName[set['edge']];
2969 iterateOverDateUnits(function(name) {
2970 if(isDefined(set[name])) {
2971 unit = name;
2972 return false;
2973 }
2974 }, 4);
2975 if(unit === 'year') set.specificity = 'month';
2976 else if(unit === 'month' || unit === 'week') set.specificity = 'day';
2977 d[(modifier.value < 0 ? 'endOf' : 'beginningOf') + simpleCapitalize(unit)] ();
2978 // This value of -2 is arbitrary but it's a nice clean way to hook into th is system.
2979 if(modifier.value === -2) d.reset();
2980 }
2981
2982 function separateAbsoluteUnits() {
2983 var params;
2984 iterateOverDateUnits(function(name, u, i) {
2985 if(name === 'day') name = 'date';
2986 if(isDefined(set[name])) {
2987 // If there is a time unit set that is more specific than
2988 // the matched unit we have a string like "5:30am in 2 minutes",
2989 // which is meaningless, so invalidate the date...
2990 if(i >= unitIndex) {
2991 invalidateDate(d);
2992 return false;
2993 }
2994 // ...otherwise set the params to set the absolute date
2995 // as a callback after the relative date has been set.
2996 params = params || {};
2997 params[name] = set[name];
2998 delete set[name];
2999 }
3000 });
3001 if(params) {
3002 afterDateSet(function() {
3003 d.set(params, true);
3004 });
3005 }
3006 }
3007
3008 d.utc(forceUTC);
3009
3010 if(isDate(f)) {
3011 // If the source here is already a date object, then the operation
3012 // is the same as cloning the date, which preserves the UTC flag.
3013 d.utc(f.isUTC()).setTime(f.getTime());
3014 } else if(isNumber(f)) {
3015 d.setTime(f);
3016 } else if(isObjectType(f)) {
3017 d.set(f, true);
3018 set = f;
3019 } else if(isString(f)) {
3020
3021 // The act of getting the localization will pre-initialize
3022 // if it is missing and add the required formats.
3023 baseLocalization = getLocalization(localeCode);
3024
3025 // Clean the input and convert Kanji based numerals if they exist.
3026 f = cleanDateInput(f);
3027
3028 if(baseLocalization) {
3029 iterateOverObject(baseLocalization.getFormats(), function(i, dif) {
3030 var match = f.match(dif.reg);
3031 if(match) {
3032
3033 loc = dif.locale;
3034 set = getFormatMatch(match, dif.to, loc);
3035 loc.cachedFormat = dif;
3036
3037
3038 if(set['utc']) {
3039 d.utc();
3040 }
3041
3042 if(set.timestamp) {
3043 set = set.timestamp;
3044 return false;
3045 }
3046
3047 // If there's a variant (crazy Endian American format), swap the mon th and day.
3048 if(dif.variant && !isString(set['month']) && (isString(set['date']) || baseLocalization.hasVariant(localeCode))) {
3049 tmp = set['month'];
3050 set['month'] = set['date'];
3051 set['date'] = tmp;
3052 }
3053
3054 // If the year is 2 digits then get the implied century.
3055 if(set['year'] && set.yearAsString.length === 2) {
3056 set['year'] = getYearFromAbbreviation(set['year']);
3057 }
3058
3059 // Set the month which may be localized.
3060 if(set['month']) {
3061 set['month'] = loc.getMonth(set['month']);
3062 if(set['shift'] && !set['unit']) set['unit'] = loc['units'][7];
3063 }
3064
3065 // If there is both a weekday and a date, the date takes precedence.
3066 if(set['weekday'] && set['date']) {
3067 delete set['weekday'];
3068 // Otherwise set a localized weekday.
3069 } else if(set['weekday']) {
3070 set['weekday'] = loc.getWeekday(set['weekday']);
3071 if(set['shift'] && !set['unit']) set['unit'] = loc['units'][5];
3072 }
3073
3074 // Relative day localizations such as "today" and "tomorrow".
3075 if(set['day'] && (tmp = loc.modifiersByName[set['day']])) {
3076 set['day'] = tmp.value;
3077 d.reset();
3078 relative = true;
3079 // If the day is a weekday, then set that instead.
3080 } else if(set['day'] && (weekday = loc.getWeekday(set['day'])) > -1) {
3081 delete set['day'];
3082 if(set['num'] && set['month']) {
3083 // If we have "the 2nd tuesday of June", set the day to the begi nning of the month, then
3084 // set the weekday after all other properties have been set. The weekday needs to be set
3085 // after the actual set because it requires overriding the "pref er" argument which
3086 // could unintentionally send the year into the future, past, et c.
3087 afterDateSet(setWeekdayOfMonth);
3088 set['day'] = 1;
3089 } else {
3090 set['weekday'] = weekday;
3091 }
3092 }
3093
3094 if(set['date'] && !isNumber(set['date'])) {
3095 set['date'] = loc.getNumericDate(set['date']);
3096 }
3097
3098 // If the time is 1pm-11pm advance the time by 12 hours.
3099 if(loc.matchPM(set['ampm']) && set['hour'] < 12) {
3100 set['hour'] += 12;
3101 } else if(loc.matchAM(set['ampm']) && set['hour'] === 12) {
3102 set['hour'] = 0;
3103 }
3104
3105 // Adjust for timezone offset
3106 if('offset_hours' in set || 'offset_minutes' in set) {
3107 d.utc();
3108 set['offset_minutes'] = set['offset_minutes'] || 0;
3109 set['offset_minutes'] += set['offset_hours'] * 60;
3110 if(set['offset_sign'] === '-') {
3111 set['offset_minutes'] *= -1;
3112 }
3113 set['minute'] -= set['offset_minutes'];
3114 }
3115
3116 // Date has a unit like "days", "months", etc. are all relative to t he current date.
3117 if(set['unit']) {
3118 relative = true;
3119 num = loc.getNumber(set['num']);
3120 unitIndex = loc.getUnitIndex(set['unit']);
3121 unit = English['units'][unitIndex];
3122
3123 // Formats like "the 15th of last month" or "6:30pm of next week"
3124 // contain absolute units in addition to relative ones, so separat e
3125 // them here, remove them from the params, and set up a callback t o
3126 // set them after the relative ones have been set.
3127 separateAbsoluteUnits();
3128
3129 // Shift and unit, ie "next month", "last week", etc.
3130 if(set['shift']) {
3131 num *= (tmp = loc.modifiersByName[set['shift']]) ? tmp.value : 0 ;
3132 }
3133
3134 // Unit and sign, ie "months ago", "weeks from now", etc.
3135 if(set['sign'] && (tmp = loc.modifiersByName[set['sign']])) {
3136 num *= tmp.value;
3137 }
3138
3139 // Units can be with non-relative dates, set here. ie "the day aft er monday"
3140 if(isDefined(set['weekday'])) {
3141 d.set({'weekday': set['weekday'] }, true);
3142 delete set['weekday'];
3143 }
3144
3145 // Finally shift the unit.
3146 set[unit] = (set[unit] || 0) + num;
3147 }
3148
3149 // If there is an "edge" it needs to be set after the
3150 // other fields are set. ie "the end of February"
3151 if(set['edge']) {
3152 afterDateSet(setUnitEdge);
3153 }
3154
3155 if(set['year_sign'] === '-') {
3156 set['year'] *= -1;
3157 }
3158
3159 iterateOverDateUnits(function(name, unit, i) {
3160 var value = set[name], fraction = value % 1;
3161 if(fraction) {
3162 set[DateUnitsReversed[i - 1].name] = round(fraction * (name === 'second' ? 1000 : 60));
3163 set[name] = floor(value);
3164 }
3165 }, 1, 4);
3166 return false;
3167 }
3168 });
3169 }
3170 if(!set) {
3171 // The Date constructor does something tricky like checking the number
3172 // of arguments so simply passing in undefined won't work.
3173 if(f !== 'now') {
3174 d = new date(f);
3175 }
3176 if(forceUTC) {
3177 // Falling back to system date here which cannot be parsed as UTC,
3178 // so if we're forcing UTC then simply add the offset.
3179 d.addMinutes(-d.getTimezoneOffset());
3180 }
3181 } else if(relative) {
3182 d.advance(set);
3183 } else {
3184 if(d._utc) {
3185 // UTC times can traverse into other days or even months,
3186 // so preemtively reset the time here to prevent this.
3187 d.reset();
3188 }
3189 updateDate(d, set, true, false, prefer);
3190 }
3191 fireCallbacks();
3192 // A date created by parsing a string presumes that the format *itself* is UTC, but
3193 // not that the date, once created, should be manipulated as such. In othe r words,
3194 // if you are creating a date object from a server time "2012-11-15T12:00: 00Z",
3195 // in the majority of cases you are using it to create a date that will, a fter creation,
3196 // be manipulated as local, so reset the utc flag here.
3197 d.utc(false);
3198 }
3199 return {
3200 date: d,
3201 set: set
3202 }
3203 }
3204
3205 // If the year is two digits, add the most appropriate century prefix.
3206 function getYearFromAbbreviation(year) {
3207 return round(callDateGet(getNewDate(), 'FullYear') / 100) * 100 - round(year / 100) * 100 + year;
3208 }
3209
3210 function getShortHour(d) {
3211 var hours = callDateGet(d, 'Hours');
3212 return hours === 0 ? 12 : hours - (floor(hours / 13) * 12);
3213 }
3214
3215 // weeksSince won't work here as the result needs to be floored, not rounded.
3216 function getWeekNumber(date) {
3217 date = date.clone();
3218 var dow = callDateGet(date, 'Day') || 7;
3219 date.addDays(4 - dow).reset();
3220 return 1 + floor(date.daysSince(date.clone().beginningOfYear()) / 7);
3221 }
3222
3223 function getAdjustedUnit(ms) {
3224 var next, ams = abs(ms), value = ams, unitIndex = 0;
3225 iterateOverDateUnits(function(name, unit, i) {
3226 next = floor(withPrecision(ams / unit.multiplier(), 1));
3227 if(next >= 1) {
3228 value = next;
3229 unitIndex = i;
3230 }
3231 }, 1);
3232 return [value, unitIndex, ms];
3233 }
3234
3235 function getRelativeWithMonthFallback(date) {
3236 var adu = getAdjustedUnit(date.millisecondsFromNow());
3237 if(allowMonthFallback(date, adu)) {
3238 // If the adjusted unit is in months, then better to use
3239 // the "monthsfromNow" which applies a special error margin
3240 // for edge cases such as Jan-09 - Mar-09 being less than
3241 // 2 months apart (when using a strict numeric definition).
3242 // The third "ms" element in the array will handle the sign
3243 // (past or future), so simply take the absolute value here.
3244 adu[0] = abs(date.monthsFromNow());
3245 adu[1] = 6;
3246 }
3247 return adu;
3248 }
3249
3250 function allowMonthFallback(date, adu) {
3251 // Allow falling back to monthsFromNow if the unit is in months...
3252 return adu[1] === 6 ||
3253 // ...or if it's === 4 weeks and there are more days than in the given month
3254 (adu[1] === 5 && adu[0] === 4 && date.daysFromNow() >= getNewDate().daysInMo nth());
3255 }
3256
3257
3258 // Date format token helpers
3259
3260 function createMeridianTokens(slice, caps) {
3261 var fn = function(d, localeCode) {
3262 var hours = callDateGet(d, 'Hours');
3263 return getLocalization(localeCode)['ampm'][floor(hours / 12)] || '';
3264 }
3265 createFormatToken('t', fn, 1);
3266 createFormatToken('tt', fn);
3267 createFormatToken('T', fn, 1, 1);
3268 createFormatToken('TT', fn, null, 2);
3269 }
3270
3271 function createWeekdayTokens(slice, caps) {
3272 var fn = function(d, localeCode) {
3273 var dow = callDateGet(d, 'Day');
3274 return getLocalization(localeCode)['weekdays'][dow];
3275 }
3276 createFormatToken('dow', fn, 3);
3277 createFormatToken('Dow', fn, 3, 1);
3278 createFormatToken('weekday', fn);
3279 createFormatToken('Weekday', fn, null, 1);
3280 }
3281
3282 function createMonthTokens(slice, caps) {
3283 createMonthToken('mon', 0, 3);
3284 createMonthToken('month', 0);
3285
3286 // For inflected month forms, namely Russian.
3287 createMonthToken('month2', 1);
3288 createMonthToken('month3', 2);
3289 }
3290
3291 function createMonthToken(token, multiplier, slice) {
3292 var fn = function(d, localeCode) {
3293 var month = callDateGet(d, 'Month');
3294 return getLocalization(localeCode)['months'][month + (multiplier * 12)];
3295 };
3296 createFormatToken(token, fn, slice);
3297 createFormatToken(simpleCapitalize(token), fn, slice, 1);
3298 }
3299
3300 function createFormatToken(t, fn, slice, caps) {
3301 DateFormatTokens[t] = function(d, localeCode) {
3302 var str = fn(d, localeCode);
3303 if(slice) str = str.slice(0, slice);
3304 if(caps) str = str.slice(0, caps).toUpperCase() + str.slice(caps);
3305 return str;
3306 }
3307 }
3308
3309 function createPaddedToken(t, fn, ms) {
3310 DateFormatTokens[t] = fn;
3311 DateFormatTokens[t + t] = function (d, localeCode) {
3312 return padNumber(fn(d, localeCode), 2);
3313 };
3314 if(ms) {
3315 DateFormatTokens[t + t + t] = function (d, localeCode) {
3316 return padNumber(fn(d, localeCode), 3);
3317 };
3318 DateFormatTokens[t + t + t + t] = function (d, localeCode) {
3319 return padNumber(fn(d, localeCode), 4);
3320 };
3321 }
3322 }
3323
3324
3325 // Date formatting helpers
3326
3327 function buildCompiledOutputFormat(format) {
3328 var match = format.match(/(\{\w+\})|[^{}]+/g);
3329 CompiledOutputFormats[format] = match.map(function(p) {
3330 p.replace(/\{(\w+)\}/, function(full, token) {
3331 p = DateFormatTokens[token] || token;
3332 return token;
3333 });
3334 return p;
3335 });
3336 }
3337
3338 function executeCompiledOutputFormat(date, format, localeCode) {
3339 var compiledFormat, length, i, t, result = '';
3340 compiledFormat = CompiledOutputFormats[format];
3341 for(i = 0, length = compiledFormat.length; i < length; i++) {
3342 t = compiledFormat[i];
3343 result += isFunction(t) ? t(date, localeCode) : t;
3344 }
3345 return result;
3346 }
3347
3348 function formatDate(date, format, relative, localeCode) {
3349 var adu;
3350 if(!date.isValid()) {
3351 return 'Invalid Date';
3352 } else if(Date[format]) {
3353 format = Date[format];
3354 } else if(isFunction(format)) {
3355 adu = getRelativeWithMonthFallback(date);
3356 format = format.apply(date, adu.concat(getLocalization(localeCode)));
3357 }
3358 if(!format && relative) {
3359 adu = adu || getRelativeWithMonthFallback(date);
3360 // Adjust up if time is in ms, as this doesn't
3361 // look very good for a standard relative date.
3362 if(adu[1] === 0) {
3363 adu[1] = 1;
3364 adu[0] = 1;
3365 }
3366 return getLocalization(localeCode).getRelativeFormat(adu);
3367 }
3368 format = format || 'long';
3369 if(format === 'short' || format === 'long' || format === 'full') {
3370 format = getLocalization(localeCode)[format];
3371 }
3372
3373 if(!CompiledOutputFormats[format]) {
3374 buildCompiledOutputFormat(format);
3375 }
3376
3377 return executeCompiledOutputFormat(date, format, localeCode);
3378 }
3379
3380 // Date comparison helpers
3381
3382 function compareDate(d, find, localeCode, buffer, forceUTC) {
3383 var p, t, min, max, override, capitalized, accuracy = 0, loBuffer = 0, hiBuf fer = 0;
3384 p = getExtendedDate(find, localeCode, null, forceUTC);
3385 if(buffer > 0) {
3386 loBuffer = hiBuffer = buffer;
3387 override = true;
3388 }
3389 if(!p.date.isValid()) return false;
3390 if(p.set && p.set.specificity) {
3391 DateUnits.forEach(function(u, i) {
3392 if(u.name === p.set.specificity) {
3393 accuracy = u.multiplier(p.date, d - p.date) - 1;
3394 }
3395 });
3396 capitalized = simpleCapitalize(p.set.specificity);
3397 if(p.set['edge'] || p.set['shift']) {
3398 p.date['beginningOf' + capitalized]();
3399 }
3400 if(p.set.specificity === 'month') {
3401 max = p.date.clone()['endOf' + capitalized]().getTime();
3402 }
3403 if(!override && p.set['sign'] && p.set.specificity != 'millisecond') {
3404 // If the time is relative, there can occasionally be an disparity betwe en the relative date
3405 // and "now", which it is being compared to, so set an extra buffer to a ccount for this.
3406 loBuffer = 50;
3407 hiBuffer = -50;
3408 }
3409 }
3410 t = d.getTime();
3411 min = p.date.getTime();
3412 max = max || (min + accuracy);
3413 max = compensateForTimezoneTraversal(d, min, max);
3414 return t >= (min - loBuffer) && t <= (max + hiBuffer);
3415 }
3416
3417 function compensateForTimezoneTraversal(d, min, max) {
3418 var dMin, dMax, minOffset, maxOffset;
3419 dMin = new date(min);
3420 dMax = new date(max).utc(d.isUTC());
3421 if(callDateGet(dMax, 'Hours') !== 23) {
3422 minOffset = dMin.getTimezoneOffset();
3423 maxOffset = dMax.getTimezoneOffset();
3424 if(minOffset !== maxOffset) {
3425 max += (maxOffset - minOffset).minutes();
3426 }
3427 }
3428 return max;
3429 }
3430
3431 function updateDate(d, params, reset, advance, prefer) {
3432 var weekday, specificityIndex;
3433
3434 function getParam(key) {
3435 return isDefined(params[key]) ? params[key] : params[key + 's'];
3436 }
3437
3438 function paramExists(key) {
3439 return isDefined(getParam(key));
3440 }
3441
3442 function uniqueParamExists(key, isDay) {
3443 return paramExists(key) || (isDay && paramExists('weekday'));
3444 }
3445
3446 function canDisambiguate() {
3447 switch(prefer) {
3448 case -1: return d > getNewDate();
3449 case 1: return d < getNewDate();
3450 }
3451 }
3452
3453 if(isNumber(params) && advance) {
3454 // If param is a number and we're advancing, the number is presumed to be milliseconds.
3455 params = { 'milliseconds': params };
3456 } else if(isNumber(params)) {
3457 // Otherwise just set the timestamp and return.
3458 d.setTime(params);
3459 return d;
3460 }
3461
3462 // "date" can also be passed for the day
3463 if(isDefined(params['date'])) {
3464 params['day'] = params['date'];
3465 }
3466
3467 // Reset any unit lower than the least specific unit set. Do not do this for weeks
3468 // or for years. This needs to be performed before the acutal setting of the date
3469 // because the order needs to be reversed in order to get the lowest specifi city,
3470 // also because higher order units can be overwritten by lower order units, such
3471 // as setting hour: 3, minute: 345, etc.
3472 iterateOverDateUnits(function(name, unit, i) {
3473 var isDay = name === 'day';
3474 if(uniqueParamExists(name, isDay)) {
3475 params.specificity = name;
3476 specificityIndex = +i;
3477 return false;
3478 } else if(reset && name !== 'week' && (!isDay || !paramExists('week'))) {
3479 // Days are relative to months, not weeks, so don't reset if a week exis ts.
3480 callDateSet(d, unit.method, (isDay ? 1 : 0));
3481 }
3482 });
3483
3484 // Now actually set or advance the date in order, higher units first.
3485 DateUnits.forEach(function(u, i) {
3486 var name = u.name, method = u.method, higherUnit = DateUnits[i - 1], value ;
3487 value = getParam(name)
3488 if(isUndefined(value)) return;
3489 if(advance) {
3490 if(name === 'week') {
3491 value = (params['day'] || 0) + (value * 7);
3492 method = 'Date';
3493 }
3494 value = (value * advance) + callDateGet(d, method);
3495 } else if(name === 'month' && paramExists('day')) {
3496 // When setting the month, there is a chance that we will traverse into a new month.
3497 // This happens in DST shifts, for example June 1st DST jumping to Janua ry 1st
3498 // (non-DST) will have a shift of -1:00 which will traverse into the pre vious year.
3499 // Prevent this by proactively setting the day when we know it will be s et again anyway.
3500 // It can also happen when there are not enough days in the target month . This second
3501 // situation is identical to checkMonthTraversal below, however when we are advancing
3502 // we want to reset the date to "the last date in the target month". In the case of
3503 // DST shifts, however, we want to avoid the "edges" of months as that i s where this
3504 // unintended traversal can happen. This is the reason for the different handling of
3505 // two similar but slightly different situations.
3506 //
3507 // TL;DR This method avoids the edges of a month IF not advancing and th e date is going
3508 // to be set anyway, while checkMonthTraversal resets the date to the la st day if advancing.
3509 //
3510 callDateSet(d, 'Date', 15);
3511 }
3512 callDateSet(d, method, value);
3513 if(advance && name === 'month') {
3514 checkMonthTraversal(d, value);
3515 }
3516 });
3517
3518
3519 // If a weekday is included in the params, set it ahead of time and set the params
3520 // to reflect the updated date so that resetting works properly.
3521 if(!advance && !paramExists('day') && paramExists('weekday')) {
3522 var weekday = getParam('weekday'), isAhead, futurePreferred;
3523 d.setWeekday(weekday);
3524 }
3525
3526 // If past or future is preferred, then the process of "disambiguation" will ensure that an
3527 // ambiguous time/date ("4pm", "thursday", "June", etc.) will be in the past or future.
3528 if(canDisambiguate()) {
3529 iterateOverDateUnits(function(name, unit) {
3530 var ambiguous = unit.ambiguous || (name === 'week' && paramExists('weekd ay'));
3531 if(ambiguous && !uniqueParamExists(name, name === 'day')) {
3532 d[unit.addMethod](prefer);
3533 return false;
3534 }
3535 }, specificityIndex + 1);
3536 }
3537 return d;
3538 }
3539
3540 // The ISO format allows times strung together without a demarcating ":", so m ake sure
3541 // that these markers are now optional.
3542 function prepareTime(format, loc, iso) {
3543 var timeSuffixMapping = {'h':0,'m':1,'s':2}, add;
3544 loc = loc || English;
3545 return format.replace(/{([a-z])}/g, function(full, token) {
3546 var separators = [],
3547 isHours = token === 'h',
3548 tokenIsRequired = isHours && !iso;
3549 if(token === 't') {
3550 return loc['ampm'].join('|');
3551 } else {
3552 if(isHours) {
3553 separators.push(':');
3554 }
3555 if(add = loc['timeSuffixes'][timeSuffixMapping[token]]) {
3556 separators.push(add + '\\s*');
3557 }
3558 return separators.length === 0 ? '' : '(?:' + separators.join('|') + ')' + (tokenIsRequired ? '' : '?');
3559 }
3560 });
3561 }
3562
3563
3564 // If the month is being set, then we don't want to accidentally
3565 // traverse into a new month just because the target month doesn't have enough
3566 // days. In other words, "5 months ago" from July 30th is still February, even
3567 // though there is no February 30th, so it will of necessity be February 28th
3568 // (or 29th in the case of a leap year).
3569
3570 function checkMonthTraversal(date, targetMonth) {
3571 if(targetMonth < 0) {
3572 targetMonth = targetMonth % 12 + 12;
3573 }
3574 if(targetMonth % 12 != callDateGet(date, 'Month')) {
3575 callDateSet(date, 'Date', 0);
3576 }
3577 }
3578
3579 function createDate(args, prefer, forceUTC) {
3580 var f, localeCode;
3581 if(isNumber(args[1])) {
3582 // If the second argument is a number, then we have an enumerated construc tor type as in "new Date(2003, 2, 12);"
3583 f = collectDateArguments(args)[0];
3584 } else {
3585 f = args[0];
3586 localeCode = args[1];
3587 }
3588 return getExtendedDate(f, localeCode, prefer, forceUTC).date;
3589 }
3590
3591 function invalidateDate(d) {
3592 d.setTime(NaN);
3593 }
3594
3595 function buildDateUnits() {
3596 DateUnitsReversed = DateUnits.concat().reverse();
3597 DateArgumentUnits = DateUnits.concat();
3598 DateArgumentUnits.splice(2,1);
3599 }
3600
3601
3602 /***
3603 * @method [units]Since([d], [locale] = currentLocale)
3604 * @returns Number
3605 * @short Returns the time since [d] in the appropriate unit.
3606 * @extra [d] will accept a date object, timestamp, or text format. If not spe cified, [d] is assumed to be now. [locale] can be passed to specify the locale t hat the date is in. %[unit]Ago% is provided as an alias to make this more readab le when [d] is assumed to be the current date. For more see @date_format.
3607 *
3608 * @set
3609 * millisecondsSince
3610 * secondsSince
3611 * minutesSince
3612 * hoursSince
3613 * daysSince
3614 * weeksSince
3615 * monthsSince
3616 * yearsSince
3617 *
3618 * @example
3619 *
3620 * Date.create().millisecondsSince('1 hour ago') -> 3,600,000
3621 * Date.create().daysSince('1 week ago') -> 7
3622 * Date.create().yearsSince('15 years ago') -> 15
3623 * Date.create('15 years ago').yearsAgo() -> 15
3624 *
3625 ***
3626 * @method [units]Ago()
3627 * @returns Number
3628 * @short Returns the time ago in the appropriate unit.
3629 *
3630 * @set
3631 * millisecondsAgo
3632 * secondsAgo
3633 * minutesAgo
3634 * hoursAgo
3635 * daysAgo
3636 * weeksAgo
3637 * monthsAgo
3638 * yearsAgo
3639 *
3640 * @example
3641 *
3642 * Date.create('last year').millisecondsAgo() -> 3,600,000
3643 * Date.create('last year').daysAgo() -> 7
3644 * Date.create('last year').yearsAgo() -> 15
3645 *
3646 ***
3647 * @method [units]Until([d], [locale] = currentLocale)
3648 * @returns Number
3649 * @short Returns the time until [d] in the appropriate unit.
3650 * @extra [d] will accept a date object, timestamp, or text format. If not spe cified, [d] is assumed to be now. [locale] can be passed to specify the locale t hat the date is in. %[unit]FromNow% is provided as an alias to make this more re adable when [d] is assumed to be the current date. For more see @date_format.
3651 *
3652 * @set
3653 * millisecondsUntil
3654 * secondsUntil
3655 * minutesUntil
3656 * hoursUntil
3657 * daysUntil
3658 * weeksUntil
3659 * monthsUntil
3660 * yearsUntil
3661 *
3662 * @example
3663 *
3664 * Date.create().millisecondsUntil('1 hour from now') -> 3,600,000
3665 * Date.create().daysUntil('1 week from now') -> 7
3666 * Date.create().yearsUntil('15 years from now') -> 15
3667 * Date.create('15 years from now').yearsFromNow() -> 15
3668 *
3669 ***
3670 * @method [units]FromNow()
3671 * @returns Number
3672 * @short Returns the time from now in the appropriate unit.
3673 *
3674 * @set
3675 * millisecondsFromNow
3676 * secondsFromNow
3677 * minutesFromNow
3678 * hoursFromNow
3679 * daysFromNow
3680 * weeksFromNow
3681 * monthsFromNow
3682 * yearsFromNow
3683 *
3684 * @example
3685 *
3686 * Date.create('next year').millisecondsFromNow() -> 3,600,000
3687 * Date.create('next year').daysFromNow() -> 7
3688 * Date.create('next year').yearsFromNow() -> 15
3689 *
3690 ***
3691 * @method add[Units](<num>, [reset] = false)
3692 * @returns Date
3693 * @short Adds <num> of the unit to the date. If [reset] is true, all lower un its will be reset.
3694 * @extra Note that "months" is ambiguous as a unit of time. If the target dat e falls on a day that does not exist (ie. August 31 -> February 31), the date wi ll be shifted to the last day of the month. Don't use %addMonths% if you need pr ecision.
3695 *
3696 * @set
3697 * addMilliseconds
3698 * addSeconds
3699 * addMinutes
3700 * addHours
3701 * addDays
3702 * addWeeks
3703 * addMonths
3704 * addYears
3705 *
3706 * @example
3707 *
3708 * Date.create().addMilliseconds(5) -> current time + 5 milliseconds
3709 * Date.create().addDays(5) -> current time + 5 days
3710 * Date.create().addYears(5) -> current time + 5 years
3711 *
3712 ***
3713 * @method isLast[Unit]()
3714 * @returns Boolean
3715 * @short Returns true if the date is last week/month/year.
3716 *
3717 * @set
3718 * isLastWeek
3719 * isLastMonth
3720 * isLastYear
3721 *
3722 * @example
3723 *
3724 * Date.create('yesterday').isLastWeek() -> true or false?
3725 * Date.create('yesterday').isLastMonth() -> probably not...
3726 * Date.create('yesterday').isLastYear() -> even less likely...
3727 *
3728 ***
3729 * @method isThis[Unit]()
3730 * @returns Boolean
3731 * @short Returns true if the date is this week/month/year.
3732 *
3733 * @set
3734 * isThisWeek
3735 * isThisMonth
3736 * isThisYear
3737 *
3738 * @example
3739 *
3740 * Date.create('tomorrow').isThisWeek() -> true or false?
3741 * Date.create('tomorrow').isThisMonth() -> probably...
3742 * Date.create('tomorrow').isThisYear() -> signs point to yes...
3743 *
3744 ***
3745 * @method isNext[Unit]()
3746 * @returns Boolean
3747 * @short Returns true if the date is next week/month/year.
3748 *
3749 * @set
3750 * isNextWeek
3751 * isNextMonth
3752 * isNextYear
3753 *
3754 * @example
3755 *
3756 * Date.create('tomorrow').isNextWeek() -> true or false?
3757 * Date.create('tomorrow').isNextMonth() -> probably not...
3758 * Date.create('tomorrow').isNextYear() -> even less likely...
3759 *
3760 ***
3761 * @method beginningOf[Unit]()
3762 * @returns Date
3763 * @short Sets the date to the beginning of the appropriate unit.
3764 *
3765 * @set
3766 * beginningOfDay
3767 * beginningOfWeek
3768 * beginningOfMonth
3769 * beginningOfYear
3770 *
3771 * @example
3772 *
3773 * Date.create().beginningOfDay() -> the beginning of today (resets the ti me)
3774 * Date.create().beginningOfWeek() -> the beginning of the week
3775 * Date.create().beginningOfMonth() -> the beginning of the month
3776 * Date.create().beginningOfYear() -> the beginning of the year
3777 *
3778 ***
3779 * @method endOf[Unit]()
3780 * @returns Date
3781 * @short Sets the date to the end of the appropriate unit.
3782 *
3783 * @set
3784 * endOfDay
3785 * endOfWeek
3786 * endOfMonth
3787 * endOfYear
3788 *
3789 * @example
3790 *
3791 * Date.create().endOfDay() -> the end of today (sets the time to 23:59:59 .999)
3792 * Date.create().endOfWeek() -> the end of the week
3793 * Date.create().endOfMonth() -> the end of the month
3794 * Date.create().endOfYear() -> the end of the year
3795 *
3796 ***/
3797
3798 function buildDateMethods() {
3799 extendSimilar(date, true, true, DateUnits, function(methods, u, i) {
3800 var name = u.name, caps = simpleCapitalize(name), multiplier = u.multiplie r(), since, until;
3801 u.addMethod = 'add' + caps + 's';
3802 // "since/until now" only count "past" an integer, i.e. "2 days ago" is
3803 // anything between 2 - 2.999 days. The default margin of error is 0.999,
3804 // but "months" have an inherently larger margin, as the number of days
3805 // in a given month may be significantly less than the number of days in
3806 // the average month, so for example "30 days" before March 15 may in fact
3807 // be 1 month ago. Years also have a margin of error due to leap years,
3808 // but this is roughly 0.999 anyway (365 / 365.25). Other units do not
3809 // technically need the error margin applied to them but this accounts
3810 // for discrepancies like (15).hoursAgo() which technically creates the
3811 // current date first, then creates a date 15 hours before and compares
3812 // them, the discrepancy between the creation of the 2 dates means that
3813 // they may actually be 15.0001 hours apart. Milliseconds don't have
3814 // fractions, so they won't be subject to this error margin.
3815 function applyErrorMargin(ms) {
3816 var num = ms / multiplier,
3817 fraction = num % 1,
3818 error = u.error || 0.999;
3819 if(fraction && abs(fraction % 1) > error) {
3820 num = round(num);
3821 }
3822 return num < 0 ? ceil(num) : floor(num);
3823 }
3824 since = function(f, localeCode) {
3825 return applyErrorMargin(this.getTime() - date.create(f, localeCode).getT ime());
3826 };
3827 until = function(f, localeCode) {
3828 return applyErrorMargin(date.create(f, localeCode).getTime() - this.getT ime());
3829 };
3830 methods[name+'sAgo'] = until;
3831 methods[name+'sUntil'] = until;
3832 methods[name+'sSince'] = since;
3833 methods[name+'sFromNow'] = since;
3834 methods[u.addMethod] = function(num, reset) {
3835 var set = {};
3836 set[name] = num;
3837 return this.advance(set, reset);
3838 };
3839 buildNumberToDateAlias(u, multiplier);
3840 if(i < 3) {
3841 ['Last','This','Next'].forEach(function(shift) {
3842 methods['is' + shift + caps] = function() {
3843 return compareDate(this, shift + ' ' + name, 'en');
3844 };
3845 });
3846 }
3847 if(i < 4) {
3848 methods['beginningOf' + caps] = function() {
3849 var set = {};
3850 switch(name) {
3851 case 'year': set['year'] = callDateGet(this, 'FullYear'); break;
3852 case 'month': set['month'] = callDateGet(this, 'Month'); break;
3853 case 'day': set['day'] = callDateGet(this, 'Date'); break;
3854 case 'week': set['weekday'] = 0; break;
3855 }
3856 return this.set(set, true);
3857 };
3858 methods['endOf' + caps] = function() {
3859 var set = { 'hours': 23, 'minutes': 59, 'seconds': 59, 'milliseconds': 999 };
3860 switch(name) {
3861 case 'year': set['month'] = 11; set['day'] = 31; break;
3862 case 'month': set['day'] = this.daysInMonth(); break;
3863 case 'week': set['weekday'] = 6; break;
3864 }
3865 return this.set(set, true);
3866 };
3867 }
3868 });
3869 }
3870
3871 function buildCoreInputFormats() {
3872 English.addFormat('([+-])?(\\d{4,4})[-.]?{full_month}[-.]?(\\d{1,2})?', true , ['year_sign','year','month','date'], false, true);
3873 English.addFormat('(\\d{1,2})[-.\\/]{full_month}(?:[-.\\/](\\d{2,4}))?', tru e, ['date','month','year'], true);
3874 English.addFormat('{full_month}[-.](\\d{4,4})', false, ['month','year']);
3875 English.addFormat('\\/Date\\((\\d+(?:[+-]\\d{4,4})?)\\)\\/', false, ['timest amp'])
3876 English.addFormat(prepareTime(RequiredTime, English), false, TimeFormat)
3877
3878 // When a new locale is initialized it will have the CoreDateFormats initial ized by default.
3879 // From there, adding new formats will push them in front of the previous on es, so the core
3880 // formats will be the last to be reached. However, the core formats themsel ves have English
3881 // months in them, which means that English needs to first be initialized an d creates a race
3882 // condition. I'm getting around this here by adding these generalized forma ts in the order
3883 // specific -> general, which will mean they will be added to the English lo calization in
3884 // general -> specific order, then chopping them off the front and reversing to get the correct
3885 // order. Note that there are 7 formats as 2 have times which adds a front a nd a back format.
3886 CoreDateFormats = English.compiledFormats.slice(0,7).reverse();
3887 English.compiledFormats = English.compiledFormats.slice(7).concat(CoreDateFo rmats);
3888 }
3889
3890 function buildFormatTokens() {
3891
3892 createPaddedToken('f', function(d) {
3893 return callDateGet(d, 'Milliseconds');
3894 }, true);
3895
3896 createPaddedToken('s', function(d) {
3897 return callDateGet(d, 'Seconds');
3898 });
3899
3900 createPaddedToken('m', function(d) {
3901 return callDateGet(d, 'Minutes');
3902 });
3903
3904 createPaddedToken('h', function(d) {
3905 return callDateGet(d, 'Hours') % 12 || 12;
3906 });
3907
3908 createPaddedToken('H', function(d) {
3909 return callDateGet(d, 'Hours');
3910 });
3911
3912 createPaddedToken('d', function(d) {
3913 return callDateGet(d, 'Date');
3914 });
3915
3916 createPaddedToken('M', function(d) {
3917 return callDateGet(d, 'Month') + 1;
3918 });
3919
3920 createMeridianTokens();
3921 createWeekdayTokens();
3922 createMonthTokens();
3923
3924 // Aliases
3925 DateFormatTokens['ms'] = DateFormatTokens['f'];
3926 DateFormatTokens['milliseconds'] = DateFormatTokens['f'];
3927 DateFormatTokens['seconds'] = DateFormatTokens['s'];
3928 DateFormatTokens['minutes'] = DateFormatTokens['m'];
3929 DateFormatTokens['hours'] = DateFormatTokens['h'];
3930 DateFormatTokens['24hr'] = DateFormatTokens['H'];
3931 DateFormatTokens['12hr'] = DateFormatTokens['h'];
3932 DateFormatTokens['date'] = DateFormatTokens['d'];
3933 DateFormatTokens['day'] = DateFormatTokens['d'];
3934 DateFormatTokens['year'] = DateFormatTokens['yyyy'];
3935
3936 }
3937
3938 function buildFormatShortcuts() {
3939 extendSimilar(date, true, true, 'short,long,full', function(methods, name) {
3940 methods[name] = function(localeCode) {
3941 return formatDate(this, name, false, localeCode);
3942 }
3943 });
3944 }
3945
3946 function buildAsianDigits() {
3947 KanjiDigits.split('').forEach(function(digit, value) {
3948 var holder;
3949 if(value > 9) {
3950 value = pow(10, value - 9);
3951 }
3952 AsianDigitMap[digit] = value;
3953 });
3954 simpleMerge(AsianDigitMap, NumberNormalizeMap);
3955 // Kanji numerals may also be included in phrases which are text-based rathe r
3956 // than actual numbers such as Chinese weekdays (上周三), and "the day before
3957 // yesterday" (一昨日) in Japanese, so don't match these.
3958 AsianDigitReg = regexp('([期週周])?([' + KanjiDigits + FullWidthDigits + ']+)(? !昨)', 'g');
3959 }
3960
3961 /***
3962 * @method is[Day]()
3963 * @returns Boolean
3964 * @short Returns true if the date falls on that day.
3965 * @extra Also available: %isYesterday%, %isToday%, %isTomorrow%, %isWeekday%, and %isWeekend%.
3966 *
3967 * @set
3968 * isToday
3969 * isYesterday
3970 * isTomorrow
3971 * isWeekday
3972 * isWeekend
3973 * isSunday
3974 * isMonday
3975 * isTuesday
3976 * isWednesday
3977 * isThursday
3978 * isFriday
3979 * isSaturday
3980 *
3981 * @example
3982 *
3983 * Date.create('tomorrow').isToday() -> false
3984 * Date.create('thursday').isTomorrow() -> ?
3985 * Date.create('yesterday').isWednesday() -> ?
3986 * Date.create('today').isWeekend() -> ?
3987 *
3988 ***
3989 * @method isFuture()
3990 * @returns Boolean
3991 * @short Returns true if the date is in the future.
3992 * @example
3993 *
3994 * Date.create('next week').isFuture() -> true
3995 * Date.create('last week').isFuture() -> false
3996 *
3997 ***
3998 * @method isPast()
3999 * @returns Boolean
4000 * @short Returns true if the date is in the past.
4001 * @example
4002 *
4003 * Date.create('last week').isPast() -> true
4004 * Date.create('next week').isPast() -> false
4005 *
4006 ***/
4007 function buildRelativeAliases() {
4008 var special = 'today,yesterday,tomorrow,weekday,weekend,future,past'.split( ',');
4009 var weekdays = English['weekdays'].slice(0,7);
4010 var months = English['months'].slice(0,12);
4011 extendSimilar(date, true, true, special.concat(weekdays).concat(months), fun ction(methods, name) {
4012 methods['is'+ simpleCapitalize(name)] = function(utc) {
4013 return this.is(name, 0, utc);
4014 };
4015 });
4016 }
4017
4018 function buildUTCAliases() {
4019 // Don't want to use extend here as it will override
4020 // the actual "utc" method on the prototype.
4021 if(date['utc']) return;
4022 date['utc'] = {
4023
4024 'create': function() {
4025 return createDate(arguments, 0, true);
4026 },
4027
4028 'past': function() {
4029 return createDate(arguments, -1, true);
4030 },
4031
4032 'future': function() {
4033 return createDate(arguments, 1, true);
4034 }
4035 };
4036 }
4037
4038 function setDateProperties() {
4039 extend(date, false , true, {
4040 'RFC1123': '{Dow}, {dd} {Mon} {yyyy} {HH}:{mm}:{ss} {tz}',
4041 'RFC1036': '{Weekday}, {dd}-{Mon}-{yy} {HH}:{mm}:{ss} {tz}',
4042 'ISO8601_DATE': '{yyyy}-{MM}-{dd}',
4043 'ISO8601_DATETIME': '{yyyy}-{MM}-{dd}T{HH}:{mm}:{ss}.{fff}{isotz}'
4044 });
4045 }
4046
4047
4048 extend(date, false, true, {
4049
4050 /***
4051 * @method Date.create(<d>, [locale] = currentLocale)
4052 * @returns Date
4053 * @short Alternate Date constructor which understands many different text f ormats, a timestamp, or another date.
4054 * @extra If no argument is given, date is assumed to be now. %Date.create% additionally can accept enumerated parameters as with the standard date construc tor. [locale] can be passed to specify the locale that the date is in. When unsp ecified, the current locale (default is English) is assumed. UTC-based dates can be created through the %utc% object. For more see @date_format.
4055 * @set
4056 * Date.utc.create
4057 *
4058 * @example
4059 *
4060 * Date.create('July') -> July of this year
4061 * Date.create('1776') -> 1776
4062 * Date.create('today') -> today
4063 * Date.create('wednesday') -> This wednesday
4064 * Date.create('next friday') -> Next friday
4065 * Date.create('July 4, 1776') -> July 4, 1776
4066 * Date.create(-446806800000) -> November 5, 1955
4067 * Date.create(1776, 6, 4) -> July 4, 1776
4068 * Date.create('1776年07月04日', 'ja') -> July 4, 1776
4069 * Date.utc.create('July 4, 1776', 'en') -> July 4, 1776
4070 *
4071 ***/
4072 'create': function() {
4073 return createDate(arguments);
4074 },
4075
4076 /***
4077 * @method Date.past(<d>, [locale] = currentLocale)
4078 * @returns Date
4079 * @short Alternate form of %Date.create% with any ambiguity assumed to be t he past.
4080 * @extra For example %"Sunday"% can be either "the Sunday coming up" or "th e Sunday last" depending on context. Note that dates explicitly in the future (" next Sunday") will remain in the future. This method simply provides a hint when ambiguity exists. UTC-based dates can be created through the %utc% object. For more, see @date_format.
4081 * @set
4082 * Date.utc.past
4083 *
4084 * @example
4085 *
4086 * Date.past('July') -> July of this year or last depending on th e current month
4087 * Date.past('Wednesday') -> This wednesday or last depending on the c urrent weekday
4088 *
4089 ***/
4090 'past': function() {
4091 return createDate(arguments, -1);
4092 },
4093
4094 /***
4095 * @method Date.future(<d>, [locale] = currentLocale)
4096 * @returns Date
4097 * @short Alternate form of %Date.create% with any ambiguity assumed to be t he future.
4098 * @extra For example %"Sunday"% can be either "the Sunday coming up" or "th e Sunday last" depending on context. Note that dates explicitly in the past ("la st Sunday") will remain in the past. This method simply provides a hint when amb iguity exists. UTC-based dates can be created through the %utc% object. For more , see @date_format.
4099 * @set
4100 * Date.utc.future
4101 *
4102 * @example
4103 *
4104 * Date.future('July') -> July of this year or next depending on the current month
4105 * Date.future('Wednesday') -> This wednesday or next depending on the current weekday
4106 *
4107 ***/
4108 'future': function() {
4109 return createDate(arguments, 1);
4110 },
4111
4112 /***
4113 * @method Date.addLocale(<code>, <set>)
4114 * @returns Locale
4115 * @short Adds a locale <set> to the locales understood by Sugar.
4116 * @extra For more see @date_format.
4117 *
4118 ***/
4119 'addLocale': function(localeCode, set) {
4120 return setLocalization(localeCode, set);
4121 },
4122
4123 /***
4124 * @method Date.setLocale(<code>)
4125 * @returns Locale
4126 * @short Sets the current locale to be used with dates.
4127 * @extra Sugar has support for 13 locales that are available through the "D ate Locales" package. In addition you can define a new locale with %Date.addLoca le%. For more see @date_format.
4128 *
4129 ***/
4130 'setLocale': function(localeCode, set) {
4131 var loc = getLocalization(localeCode, false);
4132 CurrentLocalization = loc;
4133 // The code is allowed to be more specific than the codes which are requir ed:
4134 // i.e. zh-CN or en-US. Currently this only affects US date variants such as 8/10/2000.
4135 if(localeCode && localeCode != loc['code']) {
4136 loc['code'] = localeCode;
4137 }
4138 return loc;
4139 },
4140
4141 /***
4142 * @method Date.getLocale([code] = current)
4143 * @returns Locale
4144 * @short Gets the locale for the given code, or the current locale.
4145 * @extra The resulting locale object can be manipulated to provide more con trol over date localizations. For more about locales, see @date_format.
4146 *
4147 ***/
4148 'getLocale': function(localeCode) {
4149 return !localeCode ? CurrentLocalization : getLocalization(localeCode, fal se);
4150 },
4151
4152 /**
4153 * @method Date.addFormat(<format>, <match>, [code] = null)
4154 * @returns Nothing
4155 * @short Manually adds a new date input format.
4156 * @extra This method allows fine grained control for alternate formats. <fo rmat> is a string that can have regex tokens inside. <match> is an array of the tokens that each regex capturing group will map to, for example %year%, %date%, etc. For more, see @date_format.
4157 *
4158 **/
4159 'addFormat': function(format, match, localeCode) {
4160 addDateInputFormat(getLocalization(localeCode), format, match);
4161 }
4162
4163 });
4164
4165 extend(date, true, true, {
4166
4167 /***
4168 * @method set(<set>, [reset] = false)
4169 * @returns Date
4170 * @short Sets the date object.
4171 * @extra This method can accept multiple formats including a single number as a timestamp, an object, or enumerated parameters (as with the Date constructo r). If [reset] is %true%, any units more specific than those passed will be rese t.
4172 *
4173 * @example
4174 *
4175 * new Date().set({ year: 2011, month: 11, day: 31 }) -> December 31, 2011
4176 * new Date().set(2011, 11, 31) -> December 31, 2011
4177 * new Date().set(86400000) -> 1 day after Jan 1 , 1970
4178 * new Date().set({ year: 2004, month: 6 }, true) -> June 1, 2004, 00: 00:00.000
4179 *
4180 ***/
4181 'set': function() {
4182 var args = collectDateArguments(arguments);
4183 return updateDate(this, args[0], args[1])
4184 },
4185
4186 /***
4187 * @method setWeekday()
4188 * @returns Nothing
4189 * @short Sets the weekday of the date.
4190 * @extra In order to maintain a parallel with %getWeekday% (which itself is an alias for Javascript native %getDay%), Sunday is considered day %0%. This co ntrasts with ISO-8601 standard (used in %getISOWeek% and %setISOWeek%) which pla ces Sunday at the end of the week (day 7). This effectively means that passing % 0% to this method while in the middle of a week will rewind the date, where pass ing %7% will advance it.
4191 *
4192 * @example
4193 *
4194 * d = new Date(); d.setWeekday(1); d; -> Monday of this week
4195 * d = new Date(); d.setWeekday(6); d; -> Saturday of this week
4196 *
4197 ***/
4198 'setWeekday': function(dow) {
4199 if(isUndefined(dow)) return;
4200 return callDateSet(this, 'Date', callDateGet(this, 'Date') + dow - callDat eGet(this, 'Day'));
4201 },
4202
4203 /***
4204 * @method setISOWeek()
4205 * @returns Nothing
4206 * @short Sets the week (of the year) as defined by the ISO-8601 standard.
4207 * @extra Note that this standard places Sunday at the end of the week (day 7).
4208 *
4209 * @example
4210 *
4211 * d = new Date(); d.setISOWeek(15); d; -> 15th week of the year
4212 *
4213 ***/
4214 'setISOWeek': function(week) {
4215 var weekday = callDateGet(this, 'Day') || 7;
4216 if(isUndefined(week)) return;
4217 this.set({ 'month': 0, 'date': 4 });
4218 this.set({ 'weekday': 1 });
4219 if(week > 1) {
4220 this.addWeeks(week - 1);
4221 }
4222 if(weekday !== 1) {
4223 this.advance({ 'days': weekday - 1 });
4224 }
4225 return this.getTime();
4226 },
4227
4228 /***
4229 * @method getISOWeek()
4230 * @returns Number
4231 * @short Gets the date's week (of the year) as defined by the ISO-8601 stan dard.
4232 * @extra Note that this standard places Sunday at the end of the week (day 7). If %utc% is set on the date, the week will be according to UTC time.
4233 *
4234 * @example
4235 *
4236 * new Date().getISOWeek() -> today's week of the year
4237 *
4238 ***/
4239 'getISOWeek': function() {
4240 return getWeekNumber(this);
4241 },
4242
4243 /***
4244 * @method beginningOfISOWeek()
4245 * @returns Date
4246 * @short Set the date to the beginning of week as defined by this ISO-8601 standard.
4247 * @extra Note that this standard places Monday at the start of the week.
4248 * @example
4249 *
4250 * Date.create().beginningOfISOWeek() -> Monday
4251 *
4252 ***/
4253 'beginningOfISOWeek': function() {
4254 var day = this.getDay();
4255 if(day === 0) {
4256 day = -6;
4257 } else if(day !== 1) {
4258 day = 1;
4259 }
4260 this.setWeekday(day);
4261 return this.reset();
4262 },
4263
4264 /***
4265 * @method endOfISOWeek()
4266 * @returns Date
4267 * @short Set the date to the end of week as defined by this ISO-8601 standa rd.
4268 * @extra Note that this standard places Sunday at the end of the week.
4269 * @example
4270 *
4271 * Date.create().endOfISOWeek() -> Sunday
4272 *
4273 ***/
4274 'endOfISOWeek': function() {
4275 if(this.getDay() !== 0) {
4276 this.setWeekday(7);
4277 }
4278 return this.endOfDay()
4279 },
4280
4281 /***
4282 * @method getUTCOffset([iso])
4283 * @returns String
4284 * @short Returns a string representation of the offset from UTC time. If [i so] is true the offset will be in ISO8601 format.
4285 * @example
4286 *
4287 * new Date().getUTCOffset() -> "+0900"
4288 * new Date().getUTCOffset(true) -> "+09:00"
4289 *
4290 ***/
4291 'getUTCOffset': function(iso) {
4292 var offset = this._utc ? 0 : this.getTimezoneOffset();
4293 var colon = iso === true ? ':' : '';
4294 if(!offset && iso) return 'Z';
4295 return padNumber(floor(-offset / 60), 2, true) + colon + padNumber(abs(off set % 60), 2);
4296 },
4297
4298 /***
4299 * @method utc([on] = true)
4300 * @returns Date
4301 * @short Sets the internal utc flag for the date. When on, UTC-based method s will be called internally.
4302 * @extra For more see @date_format.
4303 * @example
4304 *
4305 * new Date().utc(true)
4306 * new Date().utc(false)
4307 *
4308 ***/
4309 'utc': function(set) {
4310 defineProperty(this, '_utc', set === true || arguments.length === 0);
4311 return this;
4312 },
4313
4314 /***
4315 * @method isUTC()
4316 * @returns Boolean
4317 * @short Returns true if the date has no timezone offset.
4318 * @extra This will also return true for utc-based dates (dates that have th e %utc% method set true). Note that even if the utc flag is set, %getTimezoneOff set% will always report the same thing as Javascript always reports that based o n the environment's locale.
4319 * @example
4320 *
4321 * new Date().isUTC() -> true or false?
4322 * new Date().utc(true).isUTC() -> true
4323 *
4324 ***/
4325 'isUTC': function() {
4326 return !!this._utc || this.getTimezoneOffset() === 0;
4327 },
4328
4329 /***
4330 * @method advance(<set>, [reset] = false)
4331 * @returns Date
4332 * @short Sets the date forward.
4333 * @extra This method can accept multiple formats including an object, a str ing in the format %3 days%, a single number as milliseconds, or enumerated param eters (as with the Date constructor). If [reset] is %true%, any units more speci fic than those passed will be reset. For more see @date_format.
4334 * @example
4335 *
4336 * new Date().advance({ year: 2 }) -> 2 years in the future
4337 * new Date().advance('2 days') -> 2 days in the future
4338 * new Date().advance(0, 2, 3) -> 2 months 3 days in the future
4339 * new Date().advance(86400000) -> 1 day in the future
4340 *
4341 ***/
4342 'advance': function() {
4343 var args = collectDateArguments(arguments, true);
4344 return updateDate(this, args[0], args[1], 1);
4345 },
4346
4347 /***
4348 * @method rewind(<set>, [reset] = false)
4349 * @returns Date
4350 * @short Sets the date back.
4351 * @extra This method can accept multiple formats including a single number as a timestamp, an object, or enumerated parameters (as with the Date constructo r). If [reset] is %true%, any units more specific than those passed will be rese t. For more see @date_format.
4352 * @example
4353 *
4354 * new Date().rewind({ year: 2 }) -> 2 years in the past
4355 * new Date().rewind(0, 2, 3) -> 2 months 3 days in the past
4356 * new Date().rewind(86400000) -> 1 day in the past
4357 *
4358 ***/
4359 'rewind': function() {
4360 var args = collectDateArguments(arguments, true);
4361 return updateDate(this, args[0], args[1], -1);
4362 },
4363
4364 /***
4365 * @method isValid()
4366 * @returns Boolean
4367 * @short Returns true if the date is valid.
4368 * @example
4369 *
4370 * new Date().isValid() -> true
4371 * new Date('flexor').isValid() -> false
4372 *
4373 ***/
4374 'isValid': function() {
4375 return !isNaN(this.getTime());
4376 },
4377
4378 /***
4379 * @method isAfter(<d>, [margin] = 0)
4380 * @returns Boolean
4381 * @short Returns true if the date is after the <d>.
4382 * @extra [margin] is to allow extra margin of error (in ms). <d> will accep t a date object, timestamp, or text format. If not specified, <d> is assumed to be now. See @date_format for more.
4383 * @example
4384 *
4385 * new Date().isAfter('tomorrow') -> false
4386 * new Date().isAfter('yesterday') -> true
4387 *
4388 ***/
4389 'isAfter': function(d, margin, utc) {
4390 return this.getTime() > date.create(d).getTime() - (margin || 0);
4391 },
4392
4393 /***
4394 * @method isBefore(<d>, [margin] = 0)
4395 * @returns Boolean
4396 * @short Returns true if the date is before <d>.
4397 * @extra [margin] is to allow extra margin of error (in ms). <d> will accep t a date object, timestamp, or text format. If not specified, <d> is assumed to be now. See @date_format for more.
4398 * @example
4399 *
4400 * new Date().isBefore('tomorrow') -> true
4401 * new Date().isBefore('yesterday') -> false
4402 *
4403 ***/
4404 'isBefore': function(d, margin) {
4405 return this.getTime() < date.create(d).getTime() + (margin || 0);
4406 },
4407
4408 /***
4409 * @method isBetween(<d1>, <d2>, [margin] = 0)
4410 * @returns Boolean
4411 * @short Returns true if the date falls between <d1> and <d2>.
4412 * @extra [margin] is to allow extra margin of error (in ms). <d1> and <d2> will accept a date object, timestamp, or text format. If not specified, they are assumed to be now. See @date_format for more.
4413 * @example
4414 *
4415 * new Date().isBetween('yesterday', 'tomorrow') -> true
4416 * new Date().isBetween('last year', '2 years ago') -> false
4417 *
4418 ***/
4419 'isBetween': function(d1, d2, margin) {
4420 var t = this.getTime();
4421 var t1 = date.create(d1).getTime();
4422 var t2 = date.create(d2).getTime();
4423 var lo = min(t1, t2);
4424 var hi = max(t1, t2);
4425 margin = margin || 0;
4426 return (lo - margin < t) && (hi + margin > t);
4427 },
4428
4429 /***
4430 * @method isLeapYear()
4431 * @returns Boolean
4432 * @short Returns true if the date is a leap year.
4433 * @example
4434 *
4435 * Date.create('2000').isLeapYear() -> true
4436 *
4437 ***/
4438 'isLeapYear': function() {
4439 var year = callDateGet(this, 'FullYear');
4440 return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
4441 },
4442
4443 /***
4444 * @method daysInMonth()
4445 * @returns Number
4446 * @short Returns the number of days in the date's month.
4447 * @example
4448 *
4449 * Date.create('May').daysInMonth() -> 31
4450 * Date.create('February, 2000').daysInMonth() -> 29
4451 *
4452 ***/
4453 'daysInMonth': function() {
4454 return 32 - callDateGet(new date(callDateGet(this, 'FullYear'), callDateGe t(this, 'Month'), 32), 'Date');
4455 },
4456
4457 /***
4458 * @method format(<format>, [locale] = currentLocale)
4459 * @returns String
4460 * @short Formats and outputs the date.
4461 * @extra <format> can be a number of pre-determined formats or a string of tokens. Locale-specific formats are %short%, %long%, and %full% which have their own aliases and can be called with %date.short()%, etc. If <format> is not spec ified the %long% format is assumed. [locale] specifies a locale code to use (if not specified the current locale is used). See @date_format for more details.
4462 *
4463 * @set
4464 * short
4465 * long
4466 * full
4467 *
4468 * @example
4469 *
4470 * Date.create().format() -> ex. July 4, 2003
4471 * Date.create().format('{Weekday} {d} {Month}, {yyyy}') -> ex. Monday July 4, 2003
4472 * Date.create().format('{hh}:{mm}') -> ex. 15:57
4473 * Date.create().format('{12hr}:{mm}{tt}') -> ex. 3:57pm
4474 * Date.create().format(Date.ISO8601_DATETIME) -> ex. 2011-07 -05 12:24:55.528Z
4475 * Date.create('last week').format('short', 'ja') -> ex. 先週
4476 * Date.create('yesterday').format(function(value,unit,ms,loc) {
4477 * // value = 1, unit = 3, ms = -86400000, loc = [current locale object]
4478 * }); -> ex. 1 day a go
4479 *
4480 ***/
4481 'format': function(f, localeCode) {
4482 return formatDate(this, f, false, localeCode);
4483 },
4484
4485 /***
4486 * @method relative([fn], [locale] = currentLocale)
4487 * @returns String
4488 * @short Returns a relative date string offset to the current time.
4489 * @extra [fn] can be passed to provide for more granular control over the r esulting string. [fn] is passed 4 arguments: the adjusted value, unit, offset in milliseconds, and a localization object. As an alternate syntax, [locale] can a lso be passed as the first (and only) parameter. For more, see @date_format.
4490 * @example
4491 *
4492 * Date.create('90 seconds ago').relative() -> 1 minute ago
4493 * Date.create('January').relative() -> ex. 5 months ago
4494 * Date.create('January').relative('ja') -> 3ヶ月前
4495 * Date.create('120 minutes ago').relative(function(val,unit,ms,loc) {
4496 * // value = 2, unit = 3, ms = -7200, loc = [current locale object]
4497 * }); -> ex. 5 months ago
4498 *
4499 ***/
4500 'relative': function(fn, localeCode) {
4501 if(isString(fn)) {
4502 localeCode = fn;
4503 fn = null;
4504 }
4505 return formatDate(this, fn, true, localeCode);
4506 },
4507
4508 /***
4509 * @method is(<d>, [margin] = 0)
4510 * @returns Boolean
4511 * @short Returns true if the date is <d>.
4512 * @extra <d> will accept a date object, timestamp, or text format. %is% add itionally understands more generalized expressions like month/weekday names, 'to day', etc, and compares to the precision implied in <d>. [margin] allows an extr a margin of error in milliseconds. For more, see @date_format.
4513 * @example
4514 *
4515 * Date.create().is('July') -> true or false?
4516 * Date.create().is('1776') -> false
4517 * Date.create().is('today') -> true
4518 * Date.create().is('weekday') -> true or false?
4519 * Date.create().is('July 4, 1776') -> false
4520 * Date.create().is(-6106093200000) -> false
4521 * Date.create().is(new Date(1776, 6, 4)) -> false
4522 *
4523 ***/
4524 'is': function(d, margin, utc) {
4525 var tmp, comp;
4526 if(!this.isValid()) return;
4527 if(isString(d)) {
4528 d = d.trim().toLowerCase();
4529 comp = this.clone().utc(utc);
4530 switch(true) {
4531 case d === 'future': return this.getTime() > getNewDate().getTime();
4532 case d === 'past': return this.getTime() < getNewDate().getTime();
4533 case d === 'weekday': return callDateGet(comp, 'Day') > 0 && callDateG et(comp, 'Day') < 6;
4534 case d === 'weekend': return callDateGet(comp, 'Day') === 0 || callDat eGet(comp, 'Day') === 6;
4535 case (tmp = English['weekdays'].indexOf(d) % 7) > -1: return callDateG et(comp, 'Day') === tmp;
4536 case (tmp = English['months'].indexOf(d) % 12) > -1: return callDateG et(comp, 'Month') === tmp;
4537 }
4538 }
4539 return compareDate(this, d, null, margin, utc);
4540 },
4541
4542 /***
4543 * @method reset([unit] = 'hours')
4544 * @returns Date
4545 * @short Resets the unit passed and all smaller units. Default is "hours", effectively resetting the time.
4546 * @example
4547 *
4548 * Date.create().reset('day') -> Beginning of today
4549 * Date.create().reset('month') -> 1st of the month
4550 *
4551 ***/
4552 'reset': function(unit) {
4553 var params = {}, recognized;
4554 unit = unit || 'hours';
4555 if(unit === 'date') unit = 'days';
4556 recognized = DateUnits.some(function(u) {
4557 return unit === u.name || unit === u.name + 's';
4558 });
4559 params[unit] = unit.match(/^days?/) ? 1 : 0;
4560 return recognized ? this.set(params, true) : this;
4561 },
4562
4563 /***
4564 * @method clone()
4565 * @returns Date
4566 * @short Clones the date.
4567 * @example
4568 *
4569 * Date.create().clone() -> Copy of now
4570 *
4571 ***/
4572 'clone': function() {
4573 var d = new date(this.getTime());
4574 d.utc(!!this._utc);
4575 return d;
4576 }
4577
4578 });
4579
4580
4581 // Instance aliases
4582 extend(date, true, true, {
4583
4584 /***
4585 * @method iso()
4586 * @alias toISOString
4587 *
4588 ***/
4589 'iso': function() {
4590 return this.toISOString();
4591 },
4592
4593 /***
4594 * @method getWeekday()
4595 * @returns Number
4596 * @short Alias for %getDay%.
4597 * @set
4598 * getUTCWeekday
4599 *
4600 * @example
4601 *
4602 + Date.create().getWeekday(); -> (ex.) 3
4603 + Date.create().getUTCWeekday(); -> (ex.) 3
4604 *
4605 ***/
4606 'getWeekday': date.prototype.getDay,
4607 'getUTCWeekday': date.prototype.getUTCDay
4608
4609 });
4610
4611
4612
4613 /***
4614 * Number module
4615 *
4616 ***/
4617
4618 /***
4619 * @method [unit]()
4620 * @returns Number
4621 * @short Takes the number as a corresponding unit of time and converts to mil liseconds.
4622 * @extra Method names can be singular or plural. Note that as "a month" is a mbiguous as a unit of time, %months% will be equivalent to 30.4375 days, the ave rage number in a month. Be careful using %months% if you need exact precision.
4623 *
4624 * @set
4625 * millisecond
4626 * milliseconds
4627 * second
4628 * seconds
4629 * minute
4630 * minutes
4631 * hour
4632 * hours
4633 * day
4634 * days
4635 * week
4636 * weeks
4637 * month
4638 * months
4639 * year
4640 * years
4641 *
4642 * @example
4643 *
4644 * (5).milliseconds() -> 5
4645 * (10).hours() -> 36000000
4646 * (1).day() -> 86400000
4647 *
4648 ***
4649 * @method [unit]Before([d], [locale] = currentLocale)
4650 * @returns Date
4651 * @short Returns a date that is <n> units before [d], where <n> is the number .
4652 * @extra [d] will accept a date object, timestamp, or text format. Note that "months" is ambiguous as a unit of time. If the target date falls on a day that does not exist (ie. August 31 -> February 31), the date will be shifted to the l ast day of the month. Be careful using %monthsBefore% if you need exact precisio n. See @date_format for more.
4653 *
4654 * @set
4655 * millisecondBefore
4656 * millisecondsBefore
4657 * secondBefore
4658 * secondsBefore
4659 * minuteBefore
4660 * minutesBefore
4661 * hourBefore
4662 * hoursBefore
4663 * dayBefore
4664 * daysBefore
4665 * weekBefore
4666 * weeksBefore
4667 * monthBefore
4668 * monthsBefore
4669 * yearBefore
4670 * yearsBefore
4671 *
4672 * @example
4673 *
4674 * (5).daysBefore('tuesday') -> 5 days before tuesday of this week
4675 * (1).yearBefore('January 23, 1997') -> January 23, 1996
4676 *
4677 ***
4678 * @method [unit]Ago()
4679 * @returns Date
4680 * @short Returns a date that is <n> units ago.
4681 * @extra Note that "months" is ambiguous as a unit of time. If the target dat e falls on a day that does not exist (ie. August 31 -> February 31), the date wi ll be shifted to the last day of the month. Be careful using %monthsAgo% if you need exact precision.
4682 *
4683 * @set
4684 * millisecondAgo
4685 * millisecondsAgo
4686 * secondAgo
4687 * secondsAgo
4688 * minuteAgo
4689 * minutesAgo
4690 * hourAgo
4691 * hoursAgo
4692 * dayAgo
4693 * daysAgo
4694 * weekAgo
4695 * weeksAgo
4696 * monthAgo
4697 * monthsAgo
4698 * yearAgo
4699 * yearsAgo
4700 *
4701 * @example
4702 *
4703 * (5).weeksAgo() -> 5 weeks ago
4704 * (1).yearAgo() -> January 23, 1996
4705 *
4706 ***
4707 * @method [unit]After([d], [locale] = currentLocale)
4708 * @returns Date
4709 * @short Returns a date <n> units after [d], where <n> is the number.
4710 * @extra [d] will accept a date object, timestamp, or text format. Note that "months" is ambiguous as a unit of time. If the target date falls on a day that does not exist (ie. August 31 -> February 31), the date will be shifted to the l ast day of the month. Be careful using %monthsAfter% if you need exact precision . See @date_format for more.
4711 *
4712 * @set
4713 * millisecondAfter
4714 * millisecondsAfter
4715 * secondAfter
4716 * secondsAfter
4717 * minuteAfter
4718 * minutesAfter
4719 * hourAfter
4720 * hoursAfter
4721 * dayAfter
4722 * daysAfter
4723 * weekAfter
4724 * weeksAfter
4725 * monthAfter
4726 * monthsAfter
4727 * yearAfter
4728 * yearsAfter
4729 *
4730 * @example
4731 *
4732 * (5).daysAfter('tuesday') -> 5 days after tuesday of this week
4733 * (1).yearAfter('January 23, 1997') -> January 23, 1998
4734 *
4735 ***
4736 * @method [unit]FromNow()
4737 * @returns Date
4738 * @short Returns a date <n> units from now.
4739 * @extra Note that "months" is ambiguous as a unit of time. If the target dat e falls on a day that does not exist (ie. August 31 -> February 31), the date wi ll be shifted to the last day of the month. Be careful using %monthsFromNow% if you need exact precision.
4740 *
4741 * @set
4742 * millisecondFromNow
4743 * millisecondsFromNow
4744 * secondFromNow
4745 * secondsFromNow
4746 * minuteFromNow
4747 * minutesFromNow
4748 * hourFromNow
4749 * hoursFromNow
4750 * dayFromNow
4751 * daysFromNow
4752 * weekFromNow
4753 * weeksFromNow
4754 * monthFromNow
4755 * monthsFromNow
4756 * yearFromNow
4757 * yearsFromNow
4758 *
4759 * @example
4760 *
4761 * (5).weeksFromNow() -> 5 weeks ago
4762 * (1).yearFromNow() -> January 23, 1998
4763 *
4764 ***/
4765 function buildNumberToDateAlias(u, multiplier) {
4766 var name = u.name, methods = {};
4767 function base() { return round(this * multiplier); }
4768 function after() { return createDate(arguments)[u.addMethod](this); }
4769 function before() { return createDate(arguments)[u.addMethod](-this); }
4770 methods[name] = base;
4771 methods[name + 's'] = base;
4772 methods[name + 'Before'] = before;
4773 methods[name + 'sBefore'] = before;
4774 methods[name + 'Ago'] = before;
4775 methods[name + 'sAgo'] = before;
4776 methods[name + 'After'] = after;
4777 methods[name + 'sAfter'] = after;
4778 methods[name + 'FromNow'] = after;
4779 methods[name + 'sFromNow'] = after;
4780 number.extend(methods);
4781 }
4782
4783 extend(number, true, true, {
4784
4785 /***
4786 * @method duration([locale] = currentLocale)
4787 * @returns String
4788 * @short Takes the number as milliseconds and returns a unit-adjusted local ized string.
4789 * @extra This method is the same as %Date#relative% without the localized e quivalent of "from now" or "ago". [locale] can be passed as the first (and only) parameter. Note that this method is only available when the dates package is in cluded.
4790 * @example
4791 *
4792 * (500).duration() -> '500 milliseconds'
4793 * (1200).duration() -> '1 second'
4794 * (75).minutes().duration() -> '1 hour'
4795 * (75).minutes().duration('es') -> '1 hora'
4796 *
4797 ***/
4798 'duration': function(localeCode) {
4799 return getLocalization(localeCode).getDuration(this);
4800 }
4801
4802 });
4803
4804
4805 English = CurrentLocalization = date.addLocale('en', {
4806 'plural': true,
4807 'timeMarker': 'at',
4808 'ampm': 'am,pm',
4809 'months': 'January,February,March,April,May,June,July,August,September,O ctober,November,December',
4810 'weekdays': 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday',
4811 'units': 'millisecond:|s,second:|s,minute:|s,hour:|s,day:|s,week:|s,mon th:|s,year:|s',
4812 'numbers': 'one,two,three,four,five,six,seven,eight,nine,ten',
4813 'articles': 'a,an,the',
4814 'tokens': 'the,st|nd|rd|th,of',
4815 'short': '{Month} {d}, {yyyy}',
4816 'long': '{Month} {d}, {yyyy} {h}:{mm}{tt}',
4817 'full': '{Weekday} {Month} {d}, {yyyy} {h}:{mm}:{ss}{tt}',
4818 'past': '{num} {unit} {sign}',
4819 'future': '{num} {unit} {sign}',
4820 'duration': '{num} {unit}',
4821 'modifiers': [
4822 { 'name': 'sign', 'src': 'ago|before', 'value': -1 },
4823 { 'name': 'sign', 'src': 'from now|after|from|in|later', 'value': 1 },
4824 { 'name': 'edge', 'src': 'last day', 'value': -2 },
4825 { 'name': 'edge', 'src': 'end', 'value': -1 },
4826 { 'name': 'edge', 'src': 'first day|beginning', 'value': 1 },
4827 { 'name': 'shift', 'src': 'last', 'value': -1 },
4828 { 'name': 'shift', 'src': 'the|this', 'value': 0 },
4829 { 'name': 'shift', 'src': 'next', 'value': 1 }
4830 ],
4831 'dateParse': [
4832 '{month} {year}',
4833 '{shift} {unit=5-7}',
4834 '{0?} {date}{1}',
4835 '{0?} {edge} of {shift?} {unit=4-7?}{month?}{year?}'
4836 ],
4837 'timeParse': [
4838 '{num} {unit} {sign}',
4839 '{sign} {num} {unit}',
4840 '{0} {num}{1} {day} of {month} {year?}',
4841 '{weekday?} {month} {date}{1?} {year?}',
4842 '{date} {month} {year}',
4843 '{date} {month}',
4844 '{shift} {weekday}',
4845 '{shift} week {weekday}',
4846 '{weekday} {2?} {shift} week',
4847 '{num} {unit=4-5} {sign} {day}',
4848 '{0?} {date}{1} of {month}',
4849 '{0?}{month?} {date?}{1?} of {shift} {unit=6-7}'
4850 ]
4851 });
4852
4853 buildDateUnits();
4854 buildDateMethods();
4855 buildCoreInputFormats();
4856 buildFormatTokens();
4857 buildFormatShortcuts();
4858 buildAsianDigits();
4859 buildRelativeAliases();
4860 buildUTCAliases();
4861 setDateProperties();
4862
4863
4864 /***
4865 * @package Function
4866 * @dependency core
4867 * @description Lazy, throttled, and memoized functions, delayed functions and handling of timers, argument currying.
4868 *
4869 ***/
4870
4871 function setDelay(fn, ms, after, scope, args) {
4872 // Delay of infinity is never called of course...
4873 if(ms === Infinity) return;
4874 if(!fn.timers) fn.timers = [];
4875 if(!isNumber(ms)) ms = 1;
4876 // This is a workaround for <= IE8, which apparently has the
4877 // ability to call timeouts in the queue on the same tick (ms?)
4878 // even if functionally they have already been cleared.
4879 fn._canceled = false;
4880 fn.timers.push(setTimeout(function(){
4881 if(!fn._canceled) {
4882 after.apply(scope, args || []);
4883 }
4884 }, ms));
4885 }
4886
4887 extend(Function, true, true, {
4888
4889 /***
4890 * @method lazy([ms] = 1, [immediate] = false, [limit] = Infinity)
4891 * @returns Function
4892 * @short Creates a lazy function that, when called repeatedly, will queue e xecution and wait [ms] milliseconds to execute.
4893 * @extra If [immediate] is %true%, first execution will happen immediately, then lock. If [limit] is a fininte number, calls past [limit] will be ignored w hile execution is locked. Compare this to %throttle%, which will execute only on ce per [ms] milliseconds. Note that [ms] can also be a fraction. Calling %cancel % on a lazy function will clear the entire queue. For more see @functions.
4894 * @example
4895 *
4896 * (function() {
4897 * // Executes immediately.
4898 * }).lazy()();
4899 * (3).times(function() {
4900 * // Executes 3 times, with each execution 20ms later than the last.
4901 * }.lazy(20));
4902 * (100).times(function() {
4903 * // Executes 50 times, with each execution 20ms later than the last.
4904 * }.lazy(20, false, 50));
4905 *
4906 ***/
4907 'lazy': function(ms, immediate, limit) {
4908 var fn = this, queue = [], locked = false, execute, rounded, perExecution, result;
4909 ms = ms || 1;
4910 limit = limit || Infinity;
4911 rounded = ceil(ms);
4912 perExecution = round(rounded / ms) || 1;
4913 execute = function() {
4914 var queueLength = queue.length, maxPerRound;
4915 if(queueLength == 0) return;
4916 // Allow fractions of a millisecond by calling
4917 // multiple times per actual timeout execution
4918 maxPerRound = max(queueLength - perExecution, 0);
4919 while(queueLength > maxPerRound) {
4920 // Getting uber-meta here...
4921 result = Function.prototype.apply.apply(fn, queue.shift());
4922 queueLength--;
4923 }
4924 setDelay(lazy, rounded, function() {
4925 locked = false;
4926 execute();
4927 });
4928 }
4929 function lazy() {
4930 // If the execution has locked and it's immediate, then
4931 // allow 1 less in the queue as 1 call has already taken place.
4932 if(queue.length < limit - (locked && immediate ? 1 : 0)) {
4933 queue.push([this, arguments]);
4934 }
4935 if(!locked) {
4936 locked = true;
4937 if(immediate) {
4938 execute();
4939 } else {
4940 setDelay(lazy, rounded, execute);
4941 }
4942 }
4943 // Return the memoized result
4944 return result;
4945 }
4946 return lazy;
4947 },
4948
4949 /***
4950 * @method throttle([ms] = 1)
4951 * @returns Function
4952 * @short Creates a "throttled" version of the function that will only be ex ecuted once per <ms> milliseconds.
4953 * @extra This is functionally equivalent to calling %lazy% with a [limit] o f %1% and [immediate] as %true%. %throttle% is appropriate when you want to make sure a function is only executed at most once for a given duration. For more se e @functions.
4954 * @example
4955 *
4956 * (3).times(function() {
4957 * // called only once. will wait 50ms until it responds again
4958 * }.throttle(50));
4959 *
4960 ***/
4961 'throttle': function(ms) {
4962 return this.lazy(ms, true, 1);
4963 },
4964
4965 /***
4966 * @method debounce([ms] = 1)
4967 * @returns Function
4968 * @short Creates a "debounced" function that postpones its execution until after <ms> milliseconds have passed.
4969 * @extra This method is useful to execute a function after things have "set tled down". A good example of this is when a user tabs quickly through form fiel ds, execution of a heavy operation should happen after a few milliseconds when t hey have "settled" on a field. For more see @functions.
4970 * @example
4971 *
4972 * var fn = (function(arg1) {
4973 * // called once 50ms later
4974 * }).debounce(50); fn() fn() fn();
4975 *
4976 ***/
4977 'debounce': function(ms) {
4978 var fn = this;
4979 function debounced() {
4980 debounced.cancel();
4981 setDelay(debounced, ms, fn, this, arguments);
4982 };
4983 return debounced;
4984 },
4985
4986 /***
4987 * @method delay([ms] = 1, [arg1], ...)
4988 * @returns Function
4989 * @short Executes the function after <ms> milliseconds.
4990 * @extra Returns a reference to itself. %delay% is also a way to execute no n-blocking operations that will wait until the CPU is free. Delayed functions ca n be canceled using the %cancel% method. Can also curry arguments passed in afte r <ms>.
4991 * @example
4992 *
4993 * (function(arg1) {
4994 * // called 1s later
4995 * }).delay(1000, 'arg1');
4996 *
4997 ***/
4998 'delay': function(ms) {
4999 var fn = this;
5000 var args = multiArgs(arguments, null, 1);
5001 setDelay(fn, ms, fn, fn, args);
5002 return fn;
5003 },
5004
5005 /***
5006 * @method every([ms] = 1, [arg1], ...)
5007 * @returns Function
5008 * @short Executes the function every <ms> milliseconds.
5009 * @extra Returns a reference to itself. Repeating functions with %every% ca n be canceled using the %cancel% method. Can also curry arguments passed in afte r <ms>.
5010 * @example
5011 *
5012 * (function(arg1) {
5013 * // called every 1s
5014 * }).every(1000, 'arg1');
5015 *
5016 ***/
5017 'every': function(ms) {
5018 var fn = this, args = arguments;
5019 args = args.length > 1 ? multiArgs(args, null, 1) : [];
5020 function execute () {
5021 fn.apply(fn, args);
5022 setDelay(fn, ms, execute);
5023 }
5024 setDelay(fn, ms, execute);
5025 return fn;
5026 },
5027
5028 /***
5029 * @method cancel()
5030 * @returns Function
5031 * @short Cancels a delayed function scheduled to be run.
5032 * @extra %delay%, %lazy%, %throttle%, and %debounce% can all set delays.
5033 * @example
5034 *
5035 * (function() {
5036 * alert('hay'); // Never called
5037 * }).delay(500).cancel();
5038 *
5039 ***/
5040 'cancel': function() {
5041 var timers = this.timers, timer;
5042 if(isArray(timers)) {
5043 while(timer = timers.shift()) {
5044 clearTimeout(timer);
5045 }
5046 }
5047 this._canceled = true;
5048 return this;
5049 },
5050
5051 /***
5052 * @method after([num] = 1)
5053 * @returns Function
5054 * @short Creates a function that will execute after [num] calls.
5055 * @extra %after% is useful for running a final callback after a series of a synchronous operations, when the order in which the operations will complete is unknown.
5056 * @example
5057 *
5058 * var fn = (function() {
5059 * // Will be executed once only
5060 * }).after(3); fn(); fn(); fn();
5061 *
5062 ***/
5063 'after': function(num) {
5064 var fn = this, counter = 0, storedArguments = [];
5065 if(!isNumber(num)) {
5066 num = 1;
5067 } else if(num === 0) {
5068 fn.call();
5069 return fn;
5070 }
5071 return function() {
5072 var ret;
5073 storedArguments.push(multiArgs(arguments));
5074 counter++;
5075 if(counter == num) {
5076 ret = fn.call(this, storedArguments);
5077 counter = 0;
5078 storedArguments = [];
5079 return ret;
5080 }
5081 }
5082 },
5083
5084 /***
5085 * @method once()
5086 * @returns Function
5087 * @short Creates a function that will execute only once and store the resul t.
5088 * @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.
5089 * @example
5090 *
5091 * var fn = (function() {
5092 * // Will be executed once only
5093 * }).once(); fn(); fn(); fn();
5094 *
5095 ***/
5096 'once': function() {
5097 return this.throttle(Infinity, true);
5098 },
5099
5100 /***
5101 * @method fill(<arg1>, <arg2>, ...)
5102 * @returns Function
5103 * @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".
5104 * @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 al lows currying of arguments even when they occur toward the end of an argument li st (the example demonstrates this much more clearly).
5105 * @example
5106 *
5107 * var delayOneSecond = setTimeout.fill(undefined, 1000);
5108 * delayOneSecond(function() {
5109 * // Will be executed 1s later
5110 * });
5111 *
5112 ***/
5113 'fill': function() {
5114 var fn = this, curried = multiArgs(arguments);
5115 return function() {
5116 var args = multiArgs(arguments);
5117 curried.forEach(function(arg, index) {
5118 if(arg != null || index >= args.length) args.splice(index, 0, arg);
5119 });
5120 return fn.apply(this, args);
5121 }
5122 }
5123
5124
5125 });
5126
5127
5128 /***
5129 * @package Number
5130 * @dependency core
5131 * @description Number formatting, rounding (with precision), and ranges. Alia ses to Math methods.
5132 *
5133 ***/
5134
5135
5136 function abbreviateNumber(num, roundTo, str, mid, limit, bytes) {
5137 var fixed = num.toFixed(20),
5138 decimalPlace = fixed.search(/\./),
5139 numeralPlace = fixed.search(/[1-9]/),
5140 significant = decimalPlace - numeralPlace,
5141 unit, i, divisor;
5142 if(significant > 0) {
5143 significant -= 1;
5144 }
5145 i = max(min(floor(significant / 3), limit === false ? str.length : limit), - mid);
5146 unit = str.charAt(i + mid - 1);
5147 if(significant < -9) {
5148 i = -3;
5149 roundTo = abs(significant) - 9;
5150 unit = str.slice(0,1);
5151 }
5152 divisor = bytes ? pow(2, 10 * i) : pow(10, i * 3);
5153 return withPrecision(num / divisor, roundTo || 0).format() + unit.trim();
5154 }
5155
5156
5157 extend(number, false, true, {
5158
5159 /***
5160 * @method Number.random([n1], [n2])
5161 * @returns Number
5162 * @short Returns a random integer between [n1] and [n2].
5163 * @extra If only 1 number is passed, the other will be 0. If none are passe d, the number will be either 0 or 1.
5164 * @example
5165 *
5166 * Number.random(50, 100) -> ex. 85
5167 * Number.random(50) -> ex. 27
5168 * Number.random() -> ex. 0
5169 *
5170 ***/
5171 'random': function(n1, n2) {
5172 var minNum, maxNum;
5173 if(arguments.length == 1) n2 = n1, n1 = 0;
5174 minNum = min(n1 || 0, isUndefined(n2) ? 1 : n2);
5175 maxNum = max(n1 || 0, isUndefined(n2) ? 1 : n2) + 1;
5176 return floor((math.random() * (maxNum - minNum)) + minNum);
5177 }
5178
5179 });
5180
5181 extend(number, true, true, {
5182
5183 /***
5184 * @method log(<base> = Math.E)
5185 * @returns Number
5186 * @short Returns the logarithm of the number with base <base>, or natural l ogarithm of the number if <base> is undefined.
5187 * @example
5188 *
5189 * (64).log(2) -> 6
5190 * (9).log(3) -> 2
5191 * (5).log() -> 1.6094379124341003
5192 *
5193 ***/
5194
5195 'log': function(base) {
5196 return math.log(this) / (base ? math.log(base) : 1);
5197 },
5198
5199 /***
5200 * @method abbr([precision] = 0)
5201 * @returns String
5202 * @short Returns an abbreviated form of the number.
5203 * @extra [precision] will round to the given precision.
5204 * @example
5205 *
5206 * (1000).abbr() -> "1k"
5207 * (1000000).abbr() -> "1m"
5208 * (1280).abbr(1) -> "1.3k"
5209 *
5210 ***/
5211 'abbr': function(precision) {
5212 return abbreviateNumber(this, precision, 'kmbt', 0, 4);
5213 },
5214
5215 /***
5216 * @method metric([precision] = 0, [limit] = 1)
5217 * @returns String
5218 * @short Returns the number as a string in metric notation.
5219 * @extra [precision] will round to the given precision. Both very large num bers and very small numbers are supported. [limit] is the upper limit for the un its. 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.
5220 * @example
5221 *
5222 * (1000).metric() -> "1k"
5223 * (1000000).metric() -> "1,000k"
5224 * (1000000).metric(0, false) -> "1M"
5225 * (1249).metric(2) + 'g' -> "1.25kg"
5226 * (0.025).metric() + 'm' -> "25mm"
5227 *
5228 ***/
5229 'metric': function(precision, limit) {
5230 return abbreviateNumber(this, precision, 'nμm kMGTPE', 4, isUndefined(limi t) ? 1 : limit);
5231 },
5232
5233 /***
5234 * @method bytes([precision] = 0, [limit] = 4)
5235 * @returns String
5236 * @short Returns an abbreviated form of the number, considered to be "Bytes ".
5237 * @extra [precision] will round to the given precision. [limit] is the uppe r limit for the units. The default is %4%, which is "terabytes" (TB). If [limit] is %false%, the upper limit will be "exa".
5238 * @example
5239 *
5240 * (1000).bytes() -> "1kB"
5241 * (1000).bytes(2) -> "0.98kB"
5242 * ((10).pow(20)).bytes() -> "90,949,470TB"
5243 * ((10).pow(20)).bytes(0, false) -> "87EB"
5244 *
5245 ***/
5246 'bytes': function(precision, limit) {
5247 return abbreviateNumber(this, precision, 'kMGTPE', 0, isUndefined(limit) ? 4 : limit, true) + 'B';
5248 },
5249
5250 /***
5251 * @method isInteger()
5252 * @returns Boolean
5253 * @short Returns true if the number has no trailing decimal.
5254 * @example
5255 *
5256 * (420).isInteger() -> true
5257 * (4.5).isInteger() -> false
5258 *
5259 ***/
5260 'isInteger': function() {
5261 return this % 1 == 0;
5262 },
5263
5264 /***
5265 * @method isOdd()
5266 * @returns Boolean
5267 * @short Returns true if the number is odd.
5268 * @example
5269 *
5270 * (3).isOdd() -> true
5271 * (18).isOdd() -> false
5272 *
5273 ***/
5274 'isOdd': function() {
5275 return !isNaN(this) && !this.isMultipleOf(2);
5276 },
5277
5278 /***
5279 * @method isEven()
5280 * @returns Boolean
5281 * @short Returns true if the number is even.
5282 * @example
5283 *
5284 * (6).isEven() -> true
5285 * (17).isEven() -> false
5286 *
5287 ***/
5288 'isEven': function() {
5289 return this.isMultipleOf(2);
5290 },
5291
5292 /***
5293 * @method isMultipleOf(<num>)
5294 * @returns Boolean
5295 * @short Returns true if the number is a multiple of <num>.
5296 * @example
5297 *
5298 * (6).isMultipleOf(2) -> true
5299 * (17).isMultipleOf(2) -> false
5300 * (32).isMultipleOf(4) -> true
5301 * (34).isMultipleOf(4) -> false
5302 *
5303 ***/
5304 'isMultipleOf': function(num) {
5305 return this % num === 0;
5306 },
5307
5308
5309 /***
5310 * @method format([place] = 0, [thousands] = ',', [decimal] = '.')
5311 * @returns String
5312 * @short Formats the number to a readable string.
5313 * @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.
5314 * @example
5315 *
5316 * (56782).format() -> '56,782'
5317 * (56782).format(2) -> '56,782.00'
5318 * (4388.43).format(2, ' ') -> '4 388.43'
5319 * (4388.43).format(2, '.', ',') -> '4.388,43'
5320 *
5321 ***/
5322 'format': function(place, thousands, decimal) {
5323 var i, str, split, integer, fraction, result = '';
5324 if(isUndefined(thousands)) {
5325 thousands = ',';
5326 }
5327 if(isUndefined(decimal)) {
5328 decimal = '.';
5329 }
5330 str = (isNumber(place) ? withPrecision(this, place || 0).toFixed(max( place, 0)) : this.toString()).replace(/^-/, '');
5331 split = str.split('.');
5332 integer = split[0];
5333 fraction = split[1];
5334 for(i = integer.length; i > 0; i -= 3) {
5335 if(i < integer.length) {
5336 result = thousands + result;
5337 }
5338 result = integer.slice(max(0, i - 3), i) + result;
5339 }
5340 if(fraction) {
5341 result += decimal + repeatString('0', (place || 0) - fraction.length) + fraction;
5342 }
5343 return (this < 0 ? '-' : '') + result;
5344 },
5345
5346 /***
5347 * @method hex([pad] = 1)
5348 * @returns String
5349 * @short Converts the number to hexidecimal.
5350 * @extra [pad] will pad the resulting string to that many places.
5351 * @example
5352 *
5353 * (255).hex() -> 'ff';
5354 * (255).hex(4) -> '00ff';
5355 * (23654).hex() -> '5c66';
5356 *
5357 ***/
5358 'hex': function(pad) {
5359 return this.pad(pad || 1, false, 16);
5360 },
5361
5362 /***
5363 * @method times(<fn>)
5364 * @returns Number
5365 * @short Calls <fn> a number of times equivalent to the number.
5366 * @example
5367 *
5368 * (8).times(function(i) {
5369 * // This function is called 8 times.
5370 * });
5371 *
5372 ***/
5373 'times': function(fn) {
5374 if(fn) {
5375 for(var i = 0; i < this; i++) {
5376 fn.call(this, i);
5377 }
5378 }
5379 return this.toNumber();
5380 },
5381
5382 /***
5383 * @method chr()
5384 * @returns String
5385 * @short Returns a string at the code point of the number.
5386 * @example
5387 *
5388 * (65).chr() -> "A"
5389 * (75).chr() -> "K"
5390 *
5391 ***/
5392 'chr': function() {
5393 return string.fromCharCode(this);
5394 },
5395
5396 /***
5397 * @method pad(<place> = 0, [sign] = false, [base] = 10)
5398 * @returns String
5399 * @short Pads a number with "0" to <place>.
5400 * @extra [sign] allows you to force the sign as well (+05, etc). [base] can change the base for numeral conversion.
5401 * @example
5402 *
5403 * (5).pad(2) -> '05'
5404 * (-5).pad(4) -> '-0005'
5405 * (82).pad(3, true) -> '+082'
5406 *
5407 ***/
5408 'pad': function(place, sign, base) {
5409 return padNumber(this, place, sign, base);
5410 },
5411
5412 /***
5413 * @method ordinalize()
5414 * @returns String
5415 * @short Returns an ordinalized (English) string, i.e. "1st", "2nd", etc.
5416 * @example
5417 *
5418 * (1).ordinalize() -> '1st';
5419 * (2).ordinalize() -> '2nd';
5420 * (8).ordinalize() -> '8th';
5421 *
5422 ***/
5423 'ordinalize': function() {
5424 var suffix, num = abs(this), last = parseInt(num.toString().slice(-2));
5425 return this + getOrdinalizedSuffix(last);
5426 },
5427
5428 /***
5429 * @method toNumber()
5430 * @returns Number
5431 * @short Returns a number. This is mostly for compatibility reasons.
5432 * @example
5433 *
5434 * (420).toNumber() -> 420
5435 *
5436 ***/
5437 'toNumber': function() {
5438 return parseFloat(this, 10);
5439 }
5440
5441 });
5442
5443 /***
5444 * @method round(<precision> = 0)
5445 * @returns Number
5446 * @short Shortcut for %Math.round% that also allows a <precision>.
5447 *
5448 * @example
5449 *
5450 * (3.241).round() -> 3
5451 * (-3.841).round() -> -4
5452 * (3.241).round(2) -> 3.24
5453 * (3748).round(-2) -> 3800
5454 *
5455 ***
5456 * @method ceil(<precision> = 0)
5457 * @returns Number
5458 * @short Shortcut for %Math.ceil% that also allows a <precision>.
5459 *
5460 * @example
5461 *
5462 * (3.241).ceil() -> 4
5463 * (-3.241).ceil() -> -3
5464 * (3.241).ceil(2) -> 3.25
5465 * (3748).ceil(-2) -> 3800
5466 *
5467 ***
5468 * @method floor(<precision> = 0)
5469 * @returns Number
5470 * @short Shortcut for %Math.floor% that also allows a <precision>.
5471 *
5472 * @example
5473 *
5474 * (3.241).floor() -> 3
5475 * (-3.841).floor() -> -4
5476 * (3.241).floor(2) -> 3.24
5477 * (3748).floor(-2) -> 3700
5478 *
5479 ***
5480 * @method [math]()
5481 * @returns Number
5482 * @short Math related functions are mapped as shortcuts to numbers and are id entical. Note that %Number#log% provides some special defaults.
5483 *
5484 * @set
5485 * abs
5486 * sin
5487 * asin
5488 * cos
5489 * acos
5490 * tan
5491 * atan
5492 * sqrt
5493 * exp
5494 * pow
5495 *
5496 * @example
5497 *
5498 * (3).pow(3) -> 27
5499 * (-3).abs() -> 3
5500 * (1024).sqrt() -> 32
5501 *
5502 ***/
5503
5504 function buildNumber() {
5505 function createRoundingFunction(fn) {
5506 return function (precision) {
5507 return precision ? withPrecision(this, precision, fn) : fn(this);
5508 }
5509 }
5510 extend(number, true, true, {
5511 'ceil': createRoundingFunction(ceil),
5512 'round': createRoundingFunction(round),
5513 'floor': createRoundingFunction(floor)
5514 });
5515 extendSimilar(number, true, true, 'abs,pow,sin,asin,cos,acos,tan,atan,exp,po w,sqrt', function(methods, name) {
5516 methods[name] = function(a, b) {
5517 return math[name](this, a, b);
5518 }
5519 });
5520 }
5521
5522 buildNumber();
5523
5524
5525 /***
5526 * @package Object
5527 * @dependency core
5528 * @description Object manipulation, type checking (isNumber, isString, ...), extended objects with hash-like methods available as instance methods.
5529 *
5530 * Much thanks to kangax for his informative aricle about how problems with in stanceof and constructor
5531 * http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a- robust-isarray/
5532 *
5533 ***/
5534
5535 var ObjectTypeMethods = 'isObject,isNaN'.split(',');
5536 var ObjectHashMethods = 'keys,values,select,reject,each,merge,clone,equal,watc h,tap,has,toQueryString'.split(',');
5537
5538 function setParamsObject(obj, param, value, castBoolean) {
5539 var reg = /^(.+?)(\[.*\])$/, paramIsArray, match, allKeys, key;
5540 if(match = param.match(reg)) {
5541 key = match[1];
5542 allKeys = match[2].replace(/^\[|\]$/g, '').split('][');
5543 allKeys.forEach(function(k) {
5544 paramIsArray = !k || k.match(/^\d+$/);
5545 if(!key && isArray(obj)) key = obj.length;
5546 if(!hasOwnProperty(obj, key)) {
5547 obj[key] = paramIsArray ? [] : {};
5548 }
5549 obj = obj[key];
5550 key = k;
5551 });
5552 if(!key && paramIsArray) key = obj.length.toString();
5553 setParamsObject(obj, key, value, castBoolean);
5554 } else if(castBoolean && value === 'true') {
5555 obj[param] = true;
5556 } else if(castBoolean && value === 'false') {
5557 obj[param] = false;
5558 } else {
5559 obj[param] = value;
5560 }
5561 }
5562
5563 function objectToQueryString(base, obj) {
5564 var tmp;
5565 // If a custom toString exists bail here and use that instead
5566 if(isArray(obj) || (isObjectType(obj) && obj.toString === internalToString)) {
5567 tmp = [];
5568 iterateOverObject(obj, function(key, value) {
5569 if(base) {
5570 key = base + '[' + key + ']';
5571 }
5572 tmp.push(objectToQueryString(key, value));
5573 });
5574 return tmp.join('&');
5575 } else {
5576 if(!base) return '';
5577 return sanitizeURIComponent(base) + '=' + (isDate(obj) ? obj.getTime() : s anitizeURIComponent(obj));
5578 }
5579 }
5580
5581 function sanitizeURIComponent(obj) {
5582 // undefined, null, and NaN are represented as a blank string,
5583 // while false and 0 are stringified. "+" is allowed in query string
5584 return !obj && obj !== false && obj !== 0 ? '' : encodeURIComponent(obj).rep lace(/%20/g, '+');
5585 }
5586
5587 function matchInObject(match, key, value) {
5588 if(isRegExp(match)) {
5589 return match.test(key);
5590 } else if(isObjectType(match)) {
5591 return match[key] === value;
5592 } else {
5593 return key === string(match);
5594 }
5595 }
5596
5597 function selectFromObject(obj, args, select) {
5598 var match, result = obj instanceof Hash ? new Hash : {};
5599 iterateOverObject(obj, function(key, value) {
5600 match = false;
5601 flattenedArgs(args, function(arg) {
5602 if(matchInObject(arg, key, value)) {
5603 match = true;
5604 }
5605 }, 1);
5606 if(match === select) {
5607 result[key] = value;
5608 }
5609 });
5610 return result;
5611 }
5612
5613
5614 /***
5615 * @method Object.is[Type](<obj>)
5616 * @returns Boolean
5617 * @short Returns true if <obj> is an object of that type.
5618 * @extra %isObject% will return false on anything that is not an object liter al, including instances of inherited classes. Note also that %isNaN% will ONLY r eturn 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".
5619 *
5620 * @set
5621 * isArray
5622 * isObject
5623 * isBoolean
5624 * isDate
5625 * isFunction
5626 * isNaN
5627 * isNumber
5628 * isString
5629 * isRegExp
5630 *
5631 * @example
5632 *
5633 * Object.isArray([1,2,3]) -> true
5634 * Object.isDate(3) -> false
5635 * Object.isRegExp(/wasabi/) -> true
5636 * Object.isObject({ broken:'wear' }) -> true
5637 *
5638 ***/
5639 function buildTypeMethods() {
5640 extendSimilar(object, false, true, ClassNames, function(methods, name) {
5641 var method = 'is' + name;
5642 ObjectTypeMethods.push(method);
5643 methods[method] = typeChecks[name];
5644 });
5645 }
5646
5647 function buildObjectExtend() {
5648 extend(object, false, function(){ return arguments.length === 0; }, {
5649 'extend': function() {
5650 var methods = ObjectTypeMethods.concat(ObjectHashMethods)
5651 if(typeof EnumerableMethods !== 'undefined') {
5652 methods = methods.concat(EnumerableMethods);
5653 }
5654 buildObjectInstanceMethods(methods, object);
5655 }
5656 });
5657 }
5658
5659 extend(object, false, true, {
5660 /***
5661 * @method watch(<obj>, <prop>, <fn>)
5662 * @returns Nothing
5663 * @short Watches a property of <obj> and runs <fn> when it changes.
5664 * @extra <fn> is passed three arguments: the property <prop>, the old val ue, and the new value. The return value of [fn] will be set as the new value. Th is 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 %Objec t.defineProperty% (IE 8 and below). 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.
5665 * @example
5666 *
5667 * Object.watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
5668 * // Will be run when the property 'foo' is set on the object.
5669 * });
5670 * Object.extended().watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
5671 * // Will be run when the property 'foo' is set on the object.
5672 * });
5673 *
5674 ***/
5675 'watch': function(obj, prop, fn) {
5676 if(!definePropertySupport) return;
5677 var value = obj[prop];
5678 object.defineProperty(obj, prop, {
5679 'enumerable' : true,
5680 'configurable': true,
5681 'get': function() {
5682 return value;
5683 },
5684 'set': function(to) {
5685 value = fn.call(obj, prop, value, to);
5686 }
5687 });
5688 }
5689 });
5690
5691 extend(object, false, function() { return arguments.length > 1; }, {
5692
5693 /***
5694 * @method keys(<obj>, [fn])
5695 * @returns Array
5696 * @short Returns an array containing the keys in <obj>. Optionally calls [f n] for each key.
5697 * @extra This method is provided for browsers that don't support it nativel y, and additionally is enhanced to accept the callback [fn]. Returned keys are i n no particular order. %keys% is available as an instance method on extended obj ects.
5698 * @example
5699 *
5700 * Object.keys({ broken: 'wear' }) -> ['broken']
5701 * Object.keys({ broken: 'wear' }, function(key, value) {
5702 * // Called once for each key.
5703 * });
5704 * Object.extended({ broken: 'wear' }).keys() -> ['broken']
5705 *
5706 ***/
5707 'keys': function(obj, fn) {
5708 var keys = object.keys(obj);
5709 keys.forEach(function(key) {
5710 fn.call(obj, key, obj[key]);
5711 });
5712 return keys;
5713 }
5714
5715 });
5716
5717 extend(object, false, true, {
5718
5719 'isObject': function(obj) {
5720 return isPlainObject(obj);
5721 },
5722
5723 'isNaN': function(obj) {
5724 // This is only true of NaN
5725 return isNumber(obj) && obj.valueOf() !== obj.valueOf();
5726 },
5727
5728 /***
5729 * @method equal(<a>, <b>)
5730 * @returns Boolean
5731 * @short Returns true if <a> and <b> are equal.
5732 * @extra %equal% in Sugar is "egal", meaning the values are equal if they a re "not observably distinguishable". Note that on extended objects the name is % equals% for readability.
5733 * @example
5734 *
5735 * Object.equal({a:2}, {a:2}) -> true
5736 * Object.equal({a:2}, {a:3}) -> false
5737 * Object.extended({a:2}).equals({a:3}) -> false
5738 *
5739 ***/
5740 'equal': function(a, b) {
5741 return isEqual(a, b);
5742 },
5743
5744 /***
5745 * @method Object.extended(<obj> = {})
5746 * @returns Extended object
5747 * @short Creates a new object, equivalent to %new Object()% or %{}%, but wi th extended methods.
5748 * @extra See extended objects for more.
5749 * @example
5750 *
5751 * Object.extended()
5752 * Object.extended({ happy:true, pappy:false }).keys() -> ['happy','pappy' ]
5753 * Object.extended({ happy:true, pappy:false }).values() -> [true, false]
5754 *
5755 ***/
5756 'extended': function(obj) {
5757 return new Hash(obj);
5758 },
5759
5760 /***
5761 * @method merge(<target>, <source>, [deep] = false, [resolve] = true)
5762 * @returns Merged object
5763 * @short Merges all the properties of <source> into <target>.
5764 * @extra Merges are shallow unless [deep] is %true%. Properties of <source> will win in the case of conflicts, unless [resolve] is %false%. [resolve] can a lso be a function that resolves the conflict. In this case it will be passed 3 a rguments, %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.
5765 * @example
5766 *
5767 * Object.merge({a:1},{b:2}) -> { a:1, b:2 }
5768 * Object.merge({a:1},{a:2}, false, false) -> { a:1 }
5769 + Object.merge({a:1},{a:2}, false, function(key, a, b) {
5770 * return a + b;
5771 * }); -> { a:3 }
5772 * Object.extended({a:1}).merge({b:2}) -> { a:1, b:2 }
5773 *
5774 ***/
5775 'merge': function(target, source, deep, resolve) {
5776 var key, sourceIsObject, targetIsObject, sourceVal, targetVal, conflict, r esult;
5777 // Strings cannot be reliably merged thanks to
5778 // their properties not being enumerable in < IE8.
5779 if(target && typeof source !== 'string') {
5780 for(key in source) {
5781 if(!hasOwnProperty(source, key) || !target) continue;
5782 sourceVal = source[key];
5783 targetVal = target[key];
5784 conflict = isDefined(targetVal);
5785 sourceIsObject = isObjectType(sourceVal);
5786 targetIsObject = isObjectType(targetVal);
5787 result = conflict && resolve === false ? targetVal : sourceVal ;
5788
5789 if(conflict) {
5790 if(isFunction(resolve)) {
5791 // Use the result of the callback as the result.
5792 result = resolve.call(source, key, targetVal, sourceVal)
5793 }
5794 }
5795
5796 // Going deep
5797 if(deep && (sourceIsObject || targetIsObject)) {
5798 if(isDate(sourceVal)) {
5799 result = new date(sourceVal.getTime());
5800 } else if(isRegExp(sourceVal)) {
5801 result = new regexp(sourceVal.source, getRegExpFlags(sourceVal));
5802 } else {
5803 if(!targetIsObject) target[key] = array.isArray(sourceVal) ? [] : {};
5804 object.merge(target[key], sourceVal, deep, resolve);
5805 continue;
5806 }
5807 }
5808 target[key] = result;
5809 }
5810 }
5811 return target;
5812 },
5813
5814 /***
5815 * @method values(<obj>, [fn])
5816 * @returns Array
5817 * @short Returns an array containing the values in <obj>. Optionally calls [fn] for each value.
5818 * @extra Returned values are in no particular order. %values% is available as an instance method on extended objects.
5819 * @example
5820 *
5821 * Object.values({ broken: 'wear' }) -> ['wear']
5822 * Object.values({ broken: 'wear' }, function(value) {
5823 * // Called once for each value.
5824 * });
5825 * Object.extended({ broken: 'wear' }).values() -> ['wear']
5826 *
5827 ***/
5828 'values': function(obj, fn) {
5829 var values = [];
5830 iterateOverObject(obj, function(k,v) {
5831 values.push(v);
5832 if(fn) fn.call(obj,v);
5833 });
5834 return values;
5835 },
5836
5837 /***
5838 * @method clone(<obj> = {}, [deep] = false)
5839 * @returns Cloned object
5840 * @short Creates a clone (copy) of <obj>.
5841 * @extra Default is a shallow clone, unless [deep] is true. %clone% is avai lable as an instance method on extended objects.
5842 * @example
5843 *
5844 * Object.clone({foo:'bar'}) -> { foo: 'bar' }
5845 * Object.clone() -> {}
5846 * Object.extended({foo:'bar'}).clone() -> { foo: 'bar' }
5847 *
5848 ***/
5849 'clone': function(obj, deep) {
5850 var target, klass;
5851 if(!isObjectType(obj)) {
5852 return obj;
5853 }
5854 klass = className(obj);
5855 if(isDate(obj, klass) && obj.clone) {
5856 // Preserve internal UTC flag when applicable.
5857 return obj.clone();
5858 } else if(isDate(obj, klass) || isRegExp(obj, klass)) {
5859 return new obj.constructor(obj);
5860 } else if(obj instanceof Hash) {
5861 target = new Hash;
5862 } else if(isArray(obj, klass)) {
5863 target = [];
5864 } else if(isPlainObject(obj, klass)) {
5865 target = {};
5866 } else {
5867 throw new TypeError('Clone must be a basic data type.');
5868 }
5869 return object.merge(target, obj, deep);
5870 },
5871
5872 /***
5873 * @method Object.fromQueryString(<str>, [booleans] = false)
5874 * @returns Object
5875 * @short Converts the query string of a URL into an object.
5876 * @extra If [booleans] is true, then %"true"% and %"false"% will be cast in to booleans. All other values, including numbers will remain their string values .
5877 * @example
5878 *
5879 * Object.fromQueryString('foo=bar&broken=wear') -> { foo: 'bar', broken: 'wear' }
5880 * Object.fromQueryString('foo[]=1&foo[]=2') -> { foo: ['1','2'] }
5881 * Object.fromQueryString('foo=true', true) -> { foo: true }
5882 *
5883 ***/
5884 'fromQueryString': function(str, castBoolean) {
5885 var result = object.extended(), split;
5886 str = str && str.toString ? str.toString() : '';
5887 str.replace(/^.*?\?/, '').split('&').forEach(function(p) {
5888 var split = p.split('=');
5889 if(split.length !== 2) return;
5890 setParamsObject(result, split[0], decodeURIComponent(split[1]), castBool ean);
5891 });
5892 return result;
5893 },
5894
5895 /***
5896 * @method Object.toQueryString(<obj>, [namespace] = null)
5897 * @returns Object
5898 * @short Converts the object into a query string.
5899 * @extra Accepts deep nested objects and arrays. If [namespace] is passed, it will be prefixed to all param names.
5900 * @example
5901 *
5902 * Object.toQueryString({foo:'bar'}) -> 'foo=bar'
5903 * Object.toQueryString({foo:['a','b','c']}) -> 'foo[0]=a&foo[1]=b&foo[2] =c'
5904 * Object.toQueryString({name:'Bob'}, 'user') -> 'user[name]=Bob'
5905 *
5906 ***/
5907 'toQueryString': function(obj, namespace) {
5908 return objectToQueryString(namespace, obj);
5909 },
5910
5911 /***
5912 * @method tap(<obj>, <fn>)
5913 * @returns Object
5914 * @short Runs <fn> and returns <obj>.
5915 * @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 s tandalone 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 O bject.extend().
5916 * @example
5917 *
5918 * Object.extend();
5919 * [2,4,6].map(Math.exp).tap(function(arr) {
5920 * arr.pop()
5921 * });
5922 * [2,4,6].map(Math.exp).tap('pop').map(Math.round); -> [7,55]
5923 *
5924 ***/
5925 'tap': function(obj, arg) {
5926 var fn = arg;
5927 if(!isFunction(arg)) {
5928 fn = function() {
5929 if(arg) obj[arg]();
5930 }
5931 }
5932 fn.call(obj, obj);
5933 return obj;
5934 },
5935
5936 /***
5937 * @method has(<obj>, <key>)
5938 * @returns Boolean
5939 * @short Checks if <obj> has <key> using hasOwnProperty from Object.prototy pe.
5940 * @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-n ot-a-hash/ for more.
5941 * @example
5942 *
5943 * Object.has({ foo: 'bar' }, 'foo') -> true
5944 * Object.has({ foo: 'bar' }, 'baz') -> false
5945 * Object.has({ hasOwnProperty: true }, 'foo') -> false
5946 *
5947 ***/
5948 'has': function (obj, key) {
5949 return hasOwnProperty(obj, key);
5950 },
5951
5952 /***
5953 * @method select(<obj>, <find>, ...)
5954 * @returns Object
5955 * @short Builds a new object containing the values specified in <find>.
5956 * @extra When <find> is a string, that single key will be selected. It can also be a regex, selecting any key that matches, or an object which will match i f the key also exists in that object, effectively doing an "intersect" operation on that object. Multiple selections may also be passed as an array or directly as enumerated arguments. %select% is available as an instance method on extended objects.
5957 * @example
5958 *
5959 * Object.select({a:1,b:2}, 'a') -> {a:1}
5960 * Object.select({a:1,b:2}, /[a-z]/) -> {a:1,ba:2}
5961 * Object.select({a:1,b:2}, {a:1}) -> {a:1}
5962 * Object.select({a:1,b:2}, 'a', 'b') -> {a:1,b:2}
5963 * Object.select({a:1,b:2}, ['a', 'b']) -> {a:1,b:2}
5964 *
5965 ***/
5966 'select': function (obj) {
5967 return selectFromObject(obj, arguments, true);
5968 },
5969
5970 /***
5971 * @method reject(<obj>, <find>, ...)
5972 * @returns Object
5973 * @short Builds a new object containing all values except those specified i n <find>.
5974 * @extra When <find> is a string, that single key will be rejected. It can also be a regex, rejecting any key that matches, or an object which will match i f the key also exists in that object, effectively "subtracting" that object. Mul tiple selections may also be passed as an array or directly as enumerated argume nts. %reject% is available as an instance method on extended objects.
5975 * @example
5976 *
5977 * Object.reject({a:1,b:2}, 'a') -> {b:2}
5978 * Object.reject({a:1,b:2}, /[a-z]/) -> {}
5979 * Object.reject({a:1,b:2}, {a:1}) -> {b:2}
5980 * Object.reject({a:1,b:2}, 'a', 'b') -> {}
5981 * Object.reject({a:1,b:2}, ['a', 'b']) -> {}
5982 *
5983 ***/
5984 'reject': function (obj) {
5985 return selectFromObject(obj, arguments, false);
5986 }
5987
5988 });
5989
5990
5991 buildTypeMethods();
5992 buildObjectExtend();
5993 buildObjectInstanceMethods(ObjectHashMethods, Hash);
5994
5995 /***
5996 * @package Range
5997 * @dependency core
5998 * @description Ranges allow creating spans of numbers, strings, or dates. The y can enumerate over specific points within that range, and be manipulated and c ompared.
5999 *
6000 ***/
6001
6002 function Range(start, end) {
6003 this.start = cloneRangeMember(start);
6004 this.end = cloneRangeMember(end);
6005 };
6006
6007 function getRangeMemberNumericValue(m) {
6008 return isString(m) ? m.charCodeAt(0) : m;
6009 }
6010
6011 function getRangeMemberPrimitiveValue(m) {
6012 if(m == null) return m;
6013 return isDate(m) ? m.getTime() : m.valueOf();
6014 }
6015
6016 function cloneRangeMember(m) {
6017 if(isDate(m)) {
6018 return new date(m.getTime());
6019 } else {
6020 return getRangeMemberPrimitiveValue(m);
6021 }
6022 }
6023
6024 function isValidRangeMember(m) {
6025 var val = getRangeMemberPrimitiveValue(m);
6026 return !!val || val === 0;
6027 }
6028
6029 function getDuration(amt) {
6030 var match, val, unit;
6031 if(isNumber(amt)) {
6032 return amt;
6033 }
6034 match = amt.toLowerCase().match(/^(\d+)?\s?(\w+?)s?$/i);
6035 val = parseInt(match[1]) || 1;
6036 unit = match[2].slice(0,1).toUpperCase() + match[2].slice(1);
6037 if(unit.match(/hour|minute|second/i)) {
6038 unit += 's';
6039 } else if(unit === 'Year') {
6040 unit = 'FullYear';
6041 } else if(unit === 'Day') {
6042 unit = 'Date';
6043 }
6044 return [val, unit];
6045 }
6046
6047 function incrementDate(current, amount) {
6048 var num, unit, val, d;
6049 if(isNumber(amount)) {
6050 return new date(current.getTime() + amount);
6051 }
6052 num = amount[0];
6053 unit = amount[1];
6054 val = callDateGet(current, unit);
6055 d = new date(current.getTime());
6056 callDateSet(d, unit, val + num);
6057 return d;
6058 }
6059
6060 function incrementString(current, amount) {
6061 return string.fromCharCode(current.charCodeAt(0) + amount);
6062 }
6063
6064 function incrementNumber(current, amount) {
6065 return current + amount;
6066 }
6067
6068 /***
6069 * @method toString()
6070 * @returns String
6071 * @short Returns a string representation of the range.
6072 * @example
6073 *
6074 * Number.range(1, 5).toString() -> 1..5
6075 * Date.range(new Date(2003, 0), new Date(2005, 0)).toString() -> January 1, 2003..January 1, 2005
6076 *
6077 ***/
6078
6079 // Note: 'toString' doesn't appear in a for..in loop in IE even though
6080 // hasOwnProperty reports true, so extend() can't be used here.
6081 // Also tried simply setting the prototype = {} up front for all
6082 // methods but GCC very oddly started dropping properties in the
6083 // object randomly (maybe because of the global scope?) hence
6084 // the need for the split logic here.
6085 Range.prototype.toString = function() {
6086 return this.isValid() ? this.start + ".." + this.end : 'Invalid Range';
6087 };
6088
6089 extend(Range, true, true, {
6090
6091 /***
6092 * @method isValid()
6093 * @returns Boolean
6094 * @short Returns true if the range is valid, false otherwise.
6095 * @example
6096 *
6097 * Date.range(new Date(2003, 0), new Date(2005, 0)).isValid() -> true
6098 * Number.range(NaN, NaN).isValid() -> false
6099 *
6100 ***/
6101 'isValid': function() {
6102 return isValidRangeMember(this.start) && isValidRangeMember(this.end) && t ypeof this.start === typeof this.end;
6103 },
6104
6105 /***
6106 * @method span()
6107 * @returns Number
6108 * @short Returns the span of the range. If the range is a date range, the v alue is in milliseconds.
6109 * @extra The span includes both the start and the end.
6110 * @example
6111 *
6112 * Number.range(5, 10).span() -> 6
6113 * Date.range(new Date(2003, 0), new Date(2005, 0)).span() -> 94694400000
6114 *
6115 ***/
6116 'span': function() {
6117 return this.isValid() ? abs(
6118 getRangeMemberNumericValue(this.end) - getRangeMemberNumericValue(this.s tart)
6119 ) + 1 : NaN;
6120 },
6121
6122 /***
6123 * @method contains(<obj>)
6124 * @returns Boolean
6125 * @short Returns true if <obj> is contained inside the range. <obj> may be a value or another range.
6126 * @example
6127 *
6128 * Number.range(5, 10).contains(7) -> true
6129 * Date.range(new Date(2003, 0), new Date(2005, 0)).contains(new Date(2004 , 0)) -> true
6130 *
6131 ***/
6132 'contains': function(obj) {
6133 var self = this, arr;
6134 if(obj == null) return false;
6135 if(obj.start && obj.end) {
6136 return obj.start >= this.start && obj.start <= this.end &&
6137 obj.end >= this.start && obj.end <= this.end;
6138 } else {
6139 return obj >= this.start && obj <= this.end;
6140 }
6141 },
6142
6143 /***
6144 * @method every(<amount>, [fn])
6145 * @returns Array
6146 * @short Iterates through the range for every <amount>, calling [fn] if it is passed. Returns an array of each increment visited.
6147 * @extra In the case of date ranges, <amount> can also be a string, in whic h case it will increment a number of units. Note that %(2).months()% first reso lves to a number, which will be interpreted as milliseconds and is an approximat ion, so stepping through the actual months by passing %"2 months"% is usually pr eferable.
6148 * @example
6149 *
6150 * Number.range(2, 8).every(2) -> [2 ,4,6,8]
6151 * Date.range(new Date(2003, 1), new Date(2003,3)).every("2 months") -> [. ..]
6152 *
6153 ***/
6154 'every': function(amount, fn) {
6155 var increment,
6156 start = this.start,
6157 end = this.end,
6158 inverse = end < start,
6159 current = start,
6160 index = 0,
6161 result = [];
6162
6163 if(isFunction(amount)) {
6164 fn = amount;
6165 amount = null;
6166 }
6167 amount = amount || 1;
6168 if(isNumber(start)) {
6169 increment = incrementNumber;
6170 } else if(isString(start)) {
6171 increment = incrementString;
6172 } else if(isDate(start)) {
6173 amount = getDuration(amount);
6174 increment = incrementDate;
6175 }
6176 // Avoiding infinite loops
6177 if(inverse && amount > 0) {
6178 amount *= -1;
6179 }
6180 while(inverse ? current >= end : current <= end) {
6181 result.push(current);
6182 if(fn) {
6183 fn(current, index);
6184 }
6185 current = increment(current, amount);
6186 index++;
6187 }
6188 return result;
6189 },
6190
6191 /***
6192 * @method union(<range>)
6193 * @returns Range
6194 * @short Returns a new range with the earliest starting point as its start, and the latest ending point as its end. If the two ranges do not intersect this will effectively remove the "gap" between them.
6195 * @example
6196 *
6197 * Number.range(1, 3).union(Number.range(2, 5)) -> 1..5
6198 * Date.range(new Date(2003, 1), new Date(2005, 1)).union(Date.range(new D ate(2004, 1), new Date(2006, 1))) -> Jan 1, 2003..Jan 1, 2006
6199 *
6200 ***/
6201 'union': function(range) {
6202 return new Range(
6203 this.start < range.start ? this.start : range.start,
6204 this.end > range.end ? this.end : range.end
6205 );
6206 },
6207
6208 /***
6209 * @method intersect(<range>)
6210 * @returns Range
6211 * @short Returns a new range with the latest starting point as its start, a nd the earliest ending point as its end. If the two ranges do not intersect this will effectively produce an invalid range.
6212 * @example
6213 *
6214 * Number.range(1, 5).intersect(Number.range(4, 8)) -> 4..5
6215 * Date.range(new Date(2003, 1), new Date(2005, 1)).intersect(Date.range(n ew Date(2004, 1), new Date(2006, 1))) -> Jan 1, 2004..Jan 1, 2005
6216 *
6217 ***/
6218 'intersect': function(range) {
6219 if(range.start > this.end || range.end < this.start) {
6220 return new Range(NaN, NaN);
6221 }
6222 return new Range(
6223 this.start > range.start ? this.start : range.start,
6224 this.end < range.end ? this.end : range.end
6225 );
6226 },
6227
6228 /***
6229 * @method clone()
6230 * @returns Range
6231 * @short Clones the range.
6232 * @extra Members of the range will also be cloned.
6233 * @example
6234 *
6235 * Number.range(1, 5).clone() -> Returns a copy of the range.
6236 *
6237 ***/
6238 'clone': function(range) {
6239 return new Range(this.start, this.end);
6240 },
6241
6242 /***
6243 * @method clamp(<obj>)
6244 * @returns Mixed
6245 * @short Clamps <obj> to be within the range if it falls outside.
6246 * @example
6247 *
6248 * Number.range(1, 5).clamp(8) -> 5
6249 * Date.range(new Date(2010, 0), new Date(2012, 0)).clamp(new Date(2013, 0 )) -> 2012-01
6250 *
6251 ***/
6252 'clamp': function(obj) {
6253 var clamped,
6254 start = this.start,
6255 end = this.end,
6256 min = end < start ? end : start,
6257 max = start > end ? start : end;
6258 if(obj < min) {
6259 clamped = min;
6260 } else if(obj > max) {
6261 clamped = max;
6262 } else {
6263 clamped = obj;
6264 }
6265 return cloneRangeMember(clamped);
6266 }
6267
6268 });
6269
6270
6271 /***
6272 * Number module
6273 ***
6274 * @method Number.range([start], [end])
6275 * @returns Range
6276 * @short Creates a new range between [start] and [end]. See @ranges for more.
6277 * @example
6278 *
6279 * Number.range(5, 10)
6280 *
6281 ***
6282 * String module
6283 ***
6284 * @method String.range([start], [end])
6285 * @returns Range
6286 * @short Creates a new range between [start] and [end]. See @ranges for more.
6287 * @example
6288 *
6289 * String.range('a', 'z')
6290 *
6291 ***
6292 * Date module
6293 ***
6294 * @method Date.range([start], [end])
6295 * @returns Range
6296 * @short Creates a new range between [start] and [end].
6297 * @extra If either [start] or [end] are null, they will default to the curren t date. See @ranges for more.
6298 * @example
6299 *
6300 * Date.range('today', 'tomorrow')
6301 *
6302 ***/
6303 [number, string, date].forEach(function(klass) {
6304 extend(klass, false, true, {
6305
6306 'range': function(start, end) {
6307 if(klass.create) {
6308 start = klass.create(start);
6309 end = klass.create(end);
6310 }
6311 return new Range(start, end);
6312 }
6313
6314 });
6315
6316 });
6317
6318 /***
6319 * Number module
6320 *
6321 ***/
6322
6323 extend(number, true, true, {
6324
6325 /***
6326 * @method upto(<num>, [fn], [step] = 1)
6327 * @returns Array
6328 * @short Returns an array containing numbers from the number up to <num>.
6329 * @extra Optionally calls [fn] callback for each number in that array. [ste p] allows multiples greater than 1.
6330 * @example
6331 *
6332 * (2).upto(6) -> [2, 3, 4, 5, 6]
6333 * (2).upto(6, function(n) {
6334 * // This function is called 5 times receiving n as the value.
6335 * });
6336 * (2).upto(8, null, 2) -> [2, 4, 6, 8]
6337 *
6338 ***/
6339 'upto': function(num, fn, step) {
6340 return number.range(this, num).every(step, fn);
6341 },
6342
6343 /***
6344 * @method clamp([start] = Infinity, [end] = Infinity)
6345 * @returns Number
6346 * @short Constrains the number so that it is between [start] and [end].
6347 * @extra This will build a range object that has an equivalent %clamp% meth od.
6348 * @example
6349 *
6350 * (3).clamp(50, 100) -> 50
6351 * (85).clamp(50, 100) -> 85
6352 *
6353 ***/
6354 'clamp': function(start, end) {
6355 return new Range(start, end).clamp(this);
6356 },
6357
6358 /***
6359 * @method cap([max] = Infinity)
6360 * @returns Number
6361 * @short Constrains the number so that it is no greater than [max].
6362 * @extra This will build a range object that has an equivalent %cap% method .
6363 * @example
6364 *
6365 * (100).cap(80) -> 80
6366 *
6367 ***/
6368 'cap': function(max) {
6369 return this.clamp(Undefined, max);
6370 }
6371
6372 });
6373
6374 extend(number, true, true, {
6375
6376 /***
6377 * @method downto(<num>, [fn], [step] = 1)
6378 * @returns Array
6379 * @short Returns an array containing numbers from the number down to <num>.
6380 * @extra Optionally calls [fn] callback for each number in that array. [ste p] allows multiples greater than 1.
6381 * @example
6382 *
6383 * (8).downto(3) -> [8, 7, 6, 5, 4, 3]
6384 * (8).downto(3, function(n) {
6385 * // This function is called 6 times receiving n as the value.
6386 * });
6387 * (8).downto(2, null, 2) -> [8, 6, 4, 2]
6388 *
6389 ***/
6390 'downto': number.prototype.upto
6391
6392 });
6393
6394
6395 /***
6396 * Array module
6397 *
6398 ***/
6399
6400 extend(array, false, function(a) { return a instanceof Range; }, {
6401
6402 'create': function(range) {
6403 return range.every();
6404 }
6405
6406 });
6407
6408
6409 /***
6410 * @package RegExp
6411 * @dependency core
6412 * @description Escaping regexes and manipulating their flags.
6413 *
6414 * Note here that methods on the RegExp class like .exec and .test will fail i n the current version of SpiderMonkey being
6415 * used by CouchDB when using shorthand regex notation like /foo/. This is the reason for the intermixed use of shorthand
6416 * and compiled regexes here. If you're using JS in CouchDB, it is safer to AL WAYS compile your regexes from a string.
6417 *
6418 ***/
6419
6420 extend(regexp, false, true, {
6421
6422 /***
6423 * @method RegExp.escape(<str> = '')
6424 * @returns String
6425 * @short Escapes all RegExp tokens in a string.
6426 * @example
6427 *
6428 * RegExp.escape('really?') -> 'really\?'
6429 * RegExp.escape('yes.') -> 'yes\.'
6430 * RegExp.escape('(not really)') -> '\(not really\)'
6431 *
6432 ***/
6433 'escape': function(str) {
6434 return escapeRegExp(str);
6435 }
6436
6437 });
6438
6439 extend(regexp, true, true, {
6440
6441 /***
6442 * @method getFlags()
6443 * @returns String
6444 * @short Returns the flags of the regex as a string.
6445 * @example
6446 *
6447 * /texty/gim.getFlags('testy') -> 'gim'
6448 *
6449 ***/
6450 'getFlags': function() {
6451 return getRegExpFlags(this);
6452 },
6453
6454 /***
6455 * @method setFlags(<flags>)
6456 * @returns RegExp
6457 * @short Sets the flags on a regex and retuns a copy.
6458 * @example
6459 *
6460 * /texty/.setFlags('gim') -> now has global, ignoreCase, and multiline set
6461 *
6462 ***/
6463 'setFlags': function(flags) {
6464 return regexp(this.source, flags);
6465 },
6466
6467 /***
6468 * @method addFlag(<flag>)
6469 * @returns RegExp
6470 * @short Adds <flag> to the regex.
6471 * @example
6472 *
6473 * /texty/.addFlag('g') -> now has global flag set
6474 *
6475 ***/
6476 'addFlag': function(flag) {
6477 return this.setFlags(getRegExpFlags(this, flag));
6478 },
6479
6480 /***
6481 * @method removeFlag(<flag>)
6482 * @returns RegExp
6483 * @short Removes <flag> from the regex.
6484 * @example
6485 *
6486 * /texty/g.removeFlag('g') -> now has global flag removed
6487 *
6488 ***/
6489 'removeFlag': function(flag) {
6490 return this.setFlags(getRegExpFlags(this).replace(flag, ''));
6491 }
6492
6493 });
6494
6495
6496
6497 /***
6498 * @package String
6499 * @dependency core
6500 * @description String manupulation, escaping, encoding, truncation, and:conve rsion.
6501 *
6502 ***/
6503
6504 function getAcronym(word) {
6505 var inflector = string.Inflector;
6506 var word = inflector && inflector.acronyms[word];
6507 if(isString(word)) {
6508 return word;
6509 }
6510 }
6511
6512 function checkRepeatRange(num) {
6513 num = +num;
6514 if(num < 0 || num === Infinity) {
6515 throw new RangeError('Invalid number');
6516 }
6517 return num;
6518 }
6519
6520 function padString(num, padding) {
6521 return repeatString(isDefined(padding) ? padding : ' ', num);
6522 }
6523
6524 function truncateString(str, length, from, ellipsis, split) {
6525 var str1, str2, len1, len2;
6526 if(str.length <= length) {
6527 return str.toString();
6528 }
6529 ellipsis = isUndefined(ellipsis) ? '...' : ellipsis;
6530 switch(from) {
6531 case 'left':
6532 str2 = split ? truncateOnWord(str, length, true) : str.slice(str.length - length);
6533 return ellipsis + str2;
6534 case 'middle':
6535 len1 = ceil(length / 2);
6536 len2 = floor(length / 2);
6537 str1 = split ? truncateOnWord(str, len1) : str.slice(0, len1);
6538 str2 = split ? truncateOnWord(str, len2, true) : str.slice(str.length - len2);
6539 return str1 + ellipsis + str2;
6540 default:
6541 str1 = split ? truncateOnWord(str, length) : str.slice(0, length);
6542 return str1 + ellipsis;
6543 }
6544 }
6545
6546 function truncateOnWord(str, limit, fromLeft) {
6547 if(fromLeft) {
6548 return truncateOnWord(str.reverse(), limit).reverse();
6549 }
6550 var reg = regexp('(?=[' + getTrimmableCharacters() + '])');
6551 var words = str.split(reg);
6552 var count = 0;
6553 return words.filter(function(word) {
6554 count += word.length;
6555 return count <= limit;
6556 }).join('');
6557 }
6558
6559 function numberOrIndex(str, n, from) {
6560 if(isString(n)) {
6561 n = str.indexOf(n);
6562 if(n === -1) {
6563 n = from ? str.length : 0;
6564 }
6565 }
6566 return n;
6567 }
6568
6569 var btoa, atob;
6570
6571 function buildBase64(key) {
6572 if(globalContext.btoa) {
6573 btoa = globalContext.btoa;
6574 atob = globalContext.atob;
6575 return;
6576 }
6577 var base64reg = /[^A-Za-z0-9\+\/\=]/g;
6578 btoa = function(str) {
6579 var output = '';
6580 var chr1, chr2, chr3;
6581 var enc1, enc2, enc3, enc4;
6582 var i = 0;
6583 do {
6584 chr1 = str.charCodeAt(i++);
6585 chr2 = str.charCodeAt(i++);
6586 chr3 = str.charCodeAt(i++);
6587 enc1 = chr1 >> 2;
6588 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
6589 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
6590 enc4 = chr3 & 63;
6591 if (isNaN(chr2)) {
6592 enc3 = enc4 = 64;
6593 } else if (isNaN(chr3)) {
6594 enc4 = 64;
6595 }
6596 output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4);
6597 chr1 = chr2 = chr3 = '';
6598 enc1 = enc2 = enc3 = enc4 = '';
6599 } while (i < str.length);
6600 return output;
6601 }
6602 atob = function(input) {
6603 var output = '';
6604 var chr1, chr2, chr3;
6605 var enc1, enc2, enc3, enc4;
6606 var i = 0;
6607 if(input.match(base64reg)) {
6608 throw new Error('String contains invalid base64 characters');
6609 }
6610 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
6611 do {
6612 enc1 = key.indexOf(input.charAt(i++));
6613 enc2 = key.indexOf(input.charAt(i++));
6614 enc3 = key.indexOf(input.charAt(i++));
6615 enc4 = key.indexOf(input.charAt(i++));
6616 chr1 = (enc1 << 2) | (enc2 >> 4);
6617 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
6618 chr3 = ((enc3 & 3) << 6) | enc4;
6619 output = output + chr(chr1);
6620 if (enc3 != 64) {
6621 output = output + chr(chr2);
6622 }
6623 if (enc4 != 64) {
6624 output = output + chr(chr3);
6625 }
6626 chr1 = chr2 = chr3 = '';
6627 enc1 = enc2 = enc3 = enc4 = '';
6628 } while (i < input.length);
6629 return output;
6630 }
6631 }
6632
6633 extend(string, true, false, {
6634 /***
6635 * @method repeat([num] = 0)
6636 * @returns String
6637 * @short Returns the string repeated [num] times.
6638 * @example
6639 *
6640 * 'jumpy'.repeat(2) -> 'jumpyjumpy'
6641 * 'a'.repeat(5) -> 'aaaaa'
6642 * 'a'.repeat(0) -> ''
6643 *
6644 ***/
6645 'repeat': function(num) {
6646 num = checkRepeatRange(num);
6647 return repeatString(this, num);
6648 }
6649
6650 });
6651
6652 extend(string, true, function(reg) { return isRegExp(reg) || arguments.length > 2; }, {
6653
6654 /***
6655 * @method startsWith(<find>, [pos] = 0, [case] = true)
6656 * @returns Boolean
6657 * @short Returns true if the string starts with <find>.
6658 * @extra <find> may be either a string or regex. Search begins at [pos], wh ich defaults to the entire string. Case sensitive if [case] is true.
6659 * @example
6660 *
6661 * 'hello'.startsWith('hell') -> true
6662 * 'hello'.startsWith(/[a-h]/) -> true
6663 * 'hello'.startsWith('HELL') -> false
6664 * 'hello'.startsWith('ell', 1) -> true
6665 * 'hello'.startsWith('HELL', 0, false) -> true
6666 *
6667 ***/
6668 'startsWith': function(reg) {
6669 var args = arguments, pos = args[1], c = args[2], str = this, source;
6670 if(pos) str = str.slice(pos);
6671 if(isUndefined(c)) c = true;
6672 source = isRegExp(reg) ? reg.source.replace('^', '') : escapeRegExp(reg);
6673 return regexp('^' + source, c ? '' : 'i').test(str);
6674 },
6675
6676 /***
6677 * @method endsWith(<find>, [pos] = length, [case] = true)
6678 * @returns Boolean
6679 * @short Returns true if the string ends with <find>.
6680 * @extra <find> may be either a string or regex. Search ends at [pos], whic h defaults to the entire string. Case sensitive if [case] is true.
6681 * @example
6682 *
6683 * 'jumpy'.endsWith('py') -> true
6684 * 'jumpy'.endsWith(/[q-z]/) -> true
6685 * 'jumpy'.endsWith('MPY') -> false
6686 * 'jumpy'.endsWith('mp', 4) -> false
6687 * 'jumpy'.endsWith('MPY', 5, false) -> true
6688 *
6689 ***/
6690 'endsWith': function(reg) {
6691 var args = arguments, pos = args[1], c = args[2], str = this, source;
6692 if(isDefined(pos)) str = str.slice(0, pos);
6693 if(isUndefined(c)) c = true;
6694 source = isRegExp(reg) ? reg.source.replace('$', '') : escapeRegExp(reg);
6695 return regexp(source + '$', c ? '' : 'i').test(str);
6696 }
6697
6698 });
6699
6700 extend(string, true, true, {
6701
6702 /***
6703 * @method escapeRegExp()
6704 * @returns String
6705 * @short Escapes all RegExp tokens in the string.
6706 * @example
6707 *
6708 * 'really?'.escapeRegExp() -> 'really\?'
6709 * 'yes.'.escapeRegExp() -> 'yes\.'
6710 * '(not really)'.escapeRegExp() -> '\(not really\)'
6711 *
6712 ***/
6713 'escapeRegExp': function() {
6714 return escapeRegExp(this);
6715 },
6716
6717 /***
6718 * @method escapeURL([param] = false)
6719 * @returns String
6720 * @short Escapes characters in a string to make a valid URL.
6721 * @extra If [param] is true, it will also escape valid URL characters for use as a URL parameter.
6722 * @example
6723 *
6724 * 'http://foo.com/"bar"'.escapeURL() -> 'http://foo.com/%22bar%22'
6725 * 'http://foo.com/"bar"'.escapeURL(true) -> 'http%3A%2F%2Ffoo.com%2F%22b ar%22'
6726 *
6727 ***/
6728 'escapeURL': function(param) {
6729 return param ? encodeURIComponent(this) : encodeURI(this);
6730 },
6731
6732 /***
6733 * @method unescapeURL([partial] = false)
6734 * @returns String
6735 * @short Restores escaped characters in a URL escaped string.
6736 * @extra If [partial] is true, it will only unescape non-valid URL charact ers. [partial] is included here for completeness, but should very rarely be need ed.
6737 * @example
6738 *
6739 * 'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL() -> 'http://foo.co m/the bar'
6740 * 'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL(true) -> 'http%3A%2F%2F foo.com%2Fthe bar'
6741 *
6742 ***/
6743 'unescapeURL': function(param) {
6744 return param ? decodeURI(this) : decodeURIComponent(this);
6745 },
6746
6747 /***
6748 * @method escapeHTML()
6749 * @returns String
6750 * @short Converts HTML characters to their entity equivalents.
6751 * @example
6752 *
6753 * '<p>some text</p>'.escapeHTML() -> '&lt;p&gt;some text&lt;/p&gt;'
6754 * 'one & two'.escapeHTML() -> 'one &amp; two'
6755 *
6756 ***/
6757 'escapeHTML': function() {
6758 return this.replace(/&/g, '&amp;' )
6759 .replace(/</g, '&lt;' )
6760 .replace(/>/g, '&gt;' )
6761 .replace(/"/g, '&quot;')
6762 .replace(/'/g, '&apos;')
6763 .replace(/\//g, '&#x2f;');
6764 },
6765
6766 /***
6767 * @method unescapeHTML([partial] = false)
6768 * @returns String
6769 * @short Restores escaped HTML characters.
6770 * @example
6771 *
6772 * '&lt;p&gt;some text&lt;/p&gt;'.unescapeHTML() -> '<p>some text</p>'
6773 * 'one &amp; two'.unescapeHTML() -> 'one & two'
6774 *
6775 ***/
6776 'unescapeHTML': function() {
6777 return this.replace(/&lt;/g, '<')
6778 .replace(/&gt;/g, '>')
6779 .replace(/&quot;/g, '"')
6780 .replace(/&apos;/g, "'")
6781 .replace(/&#x2f;/g, '/')
6782 .replace(/&amp;/g, '&');
6783 },
6784
6785 /***
6786 * @method encodeBase64()
6787 * @returns String
6788 * @short Encodes the string into base64 encoding.
6789 * @extra This method wraps the browser native %btoa% when available, and u ses a custom implementation when not available. It can also handle Unicode strin g encodings.
6790 * @example
6791 *
6792 * 'gonna get encoded!'.encodeBase64() -> 'Z29ubmEgZ2V0IGVuY29kZWQh'
6793 * 'http://twitter.com/'.encodeBase64() -> 'aHR0cDovL3R3aXR0ZXIuY29tLw=='
6794 *
6795 ***/
6796 'encodeBase64': function() {
6797 return btoa(unescape(encodeURIComponent(this)));
6798 },
6799
6800 /***
6801 * @method decodeBase64()
6802 * @returns String
6803 * @short Decodes the string from base64 encoding.
6804 * @extra This method wraps the browser native %atob% when available, and u ses a custom implementation when not available. It can also handle Unicode strin g encodings.
6805 * @example
6806 *
6807 * 'aHR0cDovL3R3aXR0ZXIuY29tLw=='.decodeBase64() -> 'http://twitter.com/'
6808 * 'anVzdCBnb3QgZGVjb2RlZA=='.decodeBase64() -> 'just got decoded!'
6809 *
6810 ***/
6811 'decodeBase64': function() {
6812 return decodeURIComponent(escape(atob(this)));
6813 },
6814
6815 /***
6816 * @method each([search] = single character, [fn])
6817 * @returns Array
6818 * @short Runs callback [fn] against each occurence of [search].
6819 * @extra Returns an array of matches. [search] may be either a string or re gex, and defaults to every character in the string.
6820 * @example
6821 *
6822 * 'jumpy'.each() -> ['j','u','m','p','y']
6823 * 'jumpy'.each(/[r-z]/) -> ['u','y']
6824 * 'jumpy'.each(/[r-z]/, function(m) {
6825 * // Called twice: "u", "y"
6826 * });
6827 *
6828 ***/
6829 'each': function(search, fn) {
6830 var match, i, len;
6831 if(isFunction(search)) {
6832 fn = search;
6833 search = /[\s\S]/g;
6834 } else if(!search) {
6835 search = /[\s\S]/g
6836 } else if(isString(search)) {
6837 search = regexp(escapeRegExp(search), 'gi');
6838 } else if(isRegExp(search)) {
6839 search = regexp(search.source, getRegExpFlags(search, 'g'));
6840 }
6841 match = this.match(search) || [];
6842 if(fn) {
6843 for(i = 0, len = match.length; i < len; i++) {
6844 match[i] = fn.call(this, match[i], i, match) || match[i];
6845 }
6846 }
6847 return match;
6848 },
6849
6850 /***
6851 * @method shift(<n>)
6852 * @returns Array
6853 * @short Shifts each character in the string <n> places in the character ma p.
6854 * @example
6855 *
6856 * 'a'.shift(1) -> 'b'
6857 * 'ク'.shift(1) -> 'グ'
6858 *
6859 ***/
6860 'shift': function(n) {
6861 var result = '';
6862 n = n || 0;
6863 this.codes(function(c) {
6864 result += chr(c + n);
6865 });
6866 return result;
6867 },
6868
6869 /***
6870 * @method codes([fn])
6871 * @returns Array
6872 * @short Runs callback [fn] against each character code in the string. Retu rns an array of character codes.
6873 * @example
6874 *
6875 * 'jumpy'.codes() -> [106,117,109,112,121]
6876 * 'jumpy'.codes(function(c) {
6877 * // Called 5 times: 106, 117, 109, 112, 121
6878 * });
6879 *
6880 ***/
6881 'codes': function(fn) {
6882 var codes = [], i, len;
6883 for(i = 0, len = this.length; i < len; i++) {
6884 var code = this.charCodeAt(i);
6885 codes.push(code);
6886 if(fn) fn.call(this, code, i);
6887 }
6888 return codes;
6889 },
6890
6891 /***
6892 * @method chars([fn])
6893 * @returns Array
6894 * @short Runs callback [fn] against each character in the string. Returns a n array of characters.
6895 * @example
6896 *
6897 * 'jumpy'.chars() -> ['j','u','m','p','y']
6898 * 'jumpy'.chars(function(c) {
6899 * // Called 5 times: "j","u","m","p","y"
6900 * });
6901 *
6902 ***/
6903 'chars': function(fn) {
6904 return this.each(fn);
6905 },
6906
6907 /***
6908 * @method words([fn])
6909 * @returns Array
6910 * @short Runs callback [fn] against each word in the string. Returns an arr ay of words.
6911 * @extra A "word" here is defined as any sequence of non-whitespace charact ers.
6912 * @example
6913 *
6914 * 'broken wear'.words() -> ['broken','wear']
6915 * 'broken wear'.words(function(w) {
6916 * // Called twice: "broken", "wear"
6917 * });
6918 *
6919 ***/
6920 'words': function(fn) {
6921 return this.trim().each(/\S+/g, fn);
6922 },
6923
6924 /***
6925 * @method lines([fn])
6926 * @returns Array
6927 * @short Runs callback [fn] against each line in the string. Returns an arr ay of lines.
6928 * @example
6929 *
6930 * 'broken wear\nand\njumpy jump'.lines() -> ['broken wear','and','jumpy j ump']
6931 * 'broken wear\nand\njumpy jump'.lines(function(l) {
6932 * // Called three times: "broken wear", "and", "jumpy jump"
6933 * });
6934 *
6935 ***/
6936 'lines': function(fn) {
6937 return this.trim().each(/^.*$/gm, fn);
6938 },
6939
6940 /***
6941 * @method paragraphs([fn])
6942 * @returns Array
6943 * @short Runs callback [fn] against each paragraph in the string. Returns a n array of paragraphs.
6944 * @extra A paragraph here is defined as a block of text bounded by two or m ore line breaks.
6945 * @example
6946 *
6947 * 'Once upon a time.\n\nIn the land of oz...'.paragraphs() -> ['Once upon a time.','In the land of oz...']
6948 * 'Once upon a time.\n\nIn the land of oz...'.paragraphs(function(p) {
6949 * // Called twice: "Once upon a time.", "In teh land of oz..."
6950 * });
6951 *
6952 ***/
6953 'paragraphs': function(fn) {
6954 var paragraphs = this.trim().split(/[\r\n]{2,}/);
6955 paragraphs = paragraphs.map(function(p) {
6956 if(fn) var s = fn.call(p);
6957 return s ? s : p;
6958 });
6959 return paragraphs;
6960 },
6961
6962 /***
6963 * @method isBlank()
6964 * @returns Boolean
6965 * @short Returns true if the string has a length of 0 or contains only whit espace.
6966 * @example
6967 *
6968 * ''.isBlank() -> true
6969 * ' '.isBlank() -> true
6970 * 'noway'.isBlank() -> false
6971 *
6972 ***/
6973 'isBlank': function() {
6974 return this.trim().length === 0;
6975 },
6976
6977 /***
6978 * @method has(<find>)
6979 * @returns Boolean
6980 * @short Returns true if the string matches <find>.
6981 * @extra <find> may be a string or regex.
6982 * @example
6983 *
6984 * 'jumpy'.has('py') -> true
6985 * 'broken'.has(/[a-n]/) -> true
6986 * 'broken'.has(/[s-z]/) -> false
6987 *
6988 ***/
6989 'has': function(find) {
6990 return this.search(isRegExp(find) ? find : escapeRegExp(find)) !== -1;
6991 },
6992
6993
6994 /***
6995 * @method add(<str>, [index] = length)
6996 * @returns String
6997 * @short Adds <str> at [index]. Negative values are also allowed.
6998 * @extra %insert% is provided as an alias, and is generally more readable w hen using an index.
6999 * @example
7000 *
7001 * 'schfifty'.add(' five') -> schfifty five
7002 * 'dopamine'.insert('e', 3) -> dopeamine
7003 * 'spelling eror'.insert('r', -3) -> spelling error
7004 *
7005 ***/
7006 'add': function(str, index) {
7007 index = isUndefined(index) ? this.length : index;
7008 return this.slice(0, index) + str + this.slice(index);
7009 },
7010
7011 /***
7012 * @method remove(<f>)
7013 * @returns String
7014 * @short Removes any part of the string that matches <f>.
7015 * @extra <f> can be a string or a regex.
7016 * @example
7017 *
7018 * 'schfifty five'.remove('f') -> 'schity ive'
7019 * 'schfifty five'.remove(/[a-f]/g) -> 'shity iv'
7020 *
7021 ***/
7022 'remove': function(f) {
7023 return this.replace(f, '');
7024 },
7025
7026 /***
7027 * @method reverse()
7028 * @returns String
7029 * @short Reverses the string.
7030 * @example
7031 *
7032 * 'jumpy'.reverse() -> 'ypmuj'
7033 * 'lucky charms'.reverse() -> 'smrahc ykcul'
7034 *
7035 ***/
7036 'reverse': function() {
7037 return this.split('').reverse().join('');
7038 },
7039
7040 /***
7041 * @method compact()
7042 * @returns String
7043 * @short Compacts all white space in the string to a single space and trims the ends.
7044 * @example
7045 *
7046 * 'too \n much \n space'.compact() -> 'too much space'
7047 * 'enough \n '.compact() -> 'enought'
7048 *
7049 ***/
7050 'compact': function() {
7051 return this.trim().replace(/([\r\n\s ])+/g, function(match, whitespace){
7052 return whitespace === ' ' ? whitespace : ' ';
7053 });
7054 },
7055
7056 /***
7057 * @method at(<index>, [loop] = true)
7058 * @returns String or Array
7059 * @short Gets the character(s) at a given index.
7060 * @extra When [loop] is true, overshooting the end of the string (or the be ginning) will begin counting from the other end. As an alternate syntax, passing multiple indexes will get the characters at those indexes.
7061 * @example
7062 *
7063 * 'jumpy'.at(0) -> 'j'
7064 * 'jumpy'.at(2) -> 'm'
7065 * 'jumpy'.at(5) -> 'j'
7066 * 'jumpy'.at(5, false) -> ''
7067 * 'jumpy'.at(-1) -> 'y'
7068 * 'lucky charms'.at(2,4,6,8) -> ['u','k','y',c']
7069 *
7070 ***/
7071 'at': function() {
7072 return getEntriesForIndexes(this, arguments, true);
7073 },
7074
7075 /***
7076 * @method from([index] = 0)
7077 * @returns String
7078 * @short Returns a section of the string starting from [index].
7079 * @example
7080 *
7081 * 'lucky charms'.from() -> 'lucky charms'
7082 * 'lucky charms'.from(7) -> 'harms'
7083 *
7084 ***/
7085 'from': function(from) {
7086 return this.slice(numberOrIndex(this, from, true));
7087 },
7088
7089 /***
7090 * @method to([index] = end)
7091 * @returns String
7092 * @short Returns a section of the string ending at [index].
7093 * @example
7094 *
7095 * 'lucky charms'.to() -> 'lucky charms'
7096 * 'lucky charms'.to(7) -> 'lucky ch'
7097 *
7098 ***/
7099 'to': function(to) {
7100 if(isUndefined(to)) to = this.length;
7101 return this.slice(0, numberOrIndex(this, to));
7102 },
7103
7104 /***
7105 * @method dasherize()
7106 * @returns String
7107 * @short Converts underscores and camel casing to hypens.
7108 * @example
7109 *
7110 * 'a_farewell_to_arms'.dasherize() -> 'a-farewell-to-arms'
7111 * 'capsLock'.dasherize() -> 'caps-lock'
7112 *
7113 ***/
7114 'dasherize': function() {
7115 return this.underscore().replace(/_/g, '-');
7116 },
7117
7118 /***
7119 * @method underscore()
7120 * @returns String
7121 * @short Converts hyphens and camel casing to underscores.
7122 * @example
7123 *
7124 * 'a-farewell-to-arms'.underscore() -> 'a_farewell_to_arms'
7125 * 'capsLock'.underscore() -> 'caps_lock'
7126 *
7127 ***/
7128 'underscore': function() {
7129 return this
7130 .replace(/[-\s]+/g, '_')
7131 .replace(string.Inflector && string.Inflector.acronymRegExp, function(ac ronym, index) {
7132 return (index > 0 ? '_' : '') + acronym.toLowerCase();
7133 })
7134 .replace(/([A-Z\d]+)([A-Z][a-z])/g,'$1_$2')
7135 .replace(/([a-z\d])([A-Z])/g,'$1_$2')
7136 .toLowerCase();
7137 },
7138
7139 /***
7140 * @method camelize([first] = true)
7141 * @returns String
7142 * @short Converts underscores and hyphens to camel case. If [first] is true the first letter will also be capitalized.
7143 * @extra If the Inflections package is included acryonyms can also be defin ed that will be used when camelizing.
7144 * @example
7145 *
7146 * 'caps_lock'.camelize() -> 'CapsLock'
7147 * 'moz-border-radius'.camelize() -> 'MozBorderRadius'
7148 * 'moz-border-radius'.camelize(false) -> 'mozBorderRadius'
7149 *
7150 ***/
7151 'camelize': function(first) {
7152 return this.underscore().replace(/(^|_)([^_]+)/g, function(match, pre, wor d, index) {
7153 var acronym = getAcronym(word), capitalize = first !== false || index > 0;
7154 if(acronym) return capitalize ? acronym : acronym.toLowerCase();
7155 return capitalize ? word.capitalize() : word;
7156 });
7157 },
7158
7159 /***
7160 * @method spacify()
7161 * @returns String
7162 * @short Converts camel case, underscores, and hyphens to a properly spaced string.
7163 * @example
7164 *
7165 * 'camelCase'.spacify() -> 'camel case'
7166 * 'an-ugly-string'.spacify() -> 'an ugly string'
7167 * 'oh-no_youDid-not'.spacify().capitalize(true) -> 'something else'
7168 *
7169 ***/
7170 'spacify': function() {
7171 return this.underscore().replace(/_/g, ' ');
7172 },
7173
7174 /***
7175 * @method stripTags([tag1], [tag2], ...)
7176 * @returns String
7177 * @short Strips all HTML tags from the string.
7178 * @extra Tags to strip may be enumerated in the parameters, otherwise will strip all.
7179 * @example
7180 *
7181 * '<p>just <b>some</b> text</p>'.stripTags() -> 'just some text'
7182 * '<p>just <b>some</b> text</p>'.stripTags('p') -> 'just <b>some</b> text '
7183 *
7184 ***/
7185 'stripTags': function() {
7186 var str = this, args = arguments.length > 0 ? arguments : [''];
7187 flattenedArgs(args, function(tag) {
7188 str = str.replace(regexp('<\/?' + escapeRegExp(tag) + '[^<>]*>', 'gi'), '');
7189 });
7190 return str;
7191 },
7192
7193 /***
7194 * @method removeTags([tag1], [tag2], ...)
7195 * @returns String
7196 * @short Removes all HTML tags and their contents from the string.
7197 * @extra Tags to remove may be enumerated in the parameters, otherwise will remove all.
7198 * @example
7199 *
7200 * '<p>just <b>some</b> text</p>'.removeTags() -> ''
7201 * '<p>just <b>some</b> text</p>'.removeTags('b') -> '<p>just text</p>'
7202 *
7203 ***/
7204 'removeTags': function() {
7205 var str = this, args = arguments.length > 0 ? arguments : ['\\S+'];
7206 flattenedArgs(args, function(t) {
7207 var reg = regexp('<(' + t + ')[^<>]*(?:\\/>|>.*?<\\/\\1>)', 'gi');
7208 str = str.replace(reg, '');
7209 });
7210 return str;
7211 },
7212
7213 /***
7214 * @method truncate(<length>, [from] = 'right', [ellipsis] = '...')
7215 * @returns String
7216 * @short Truncates a string.
7217 * @extra [from] can be %'right'%, %'left'%, or %'middle'%. If the string is shorter than <length>, [ellipsis] will not be added.
7218 * @example
7219 *
7220 * 'sittin on the dock of the bay'.truncate(18) -> 'just sittin on the do...'
7221 * 'sittin on the dock of the bay'.truncate(18, 'left') -> '...the dock of the bay'
7222 * 'sittin on the dock of the bay'.truncate(18, 'middle') -> 'just sitt... of the bay'
7223 *
7224 ***/
7225 'truncate': function(length, from, ellipsis) {
7226 return truncateString(this, length, from, ellipsis);
7227 },
7228
7229 /***
7230 * @method truncateOnWord(<length>, [from] = 'right', [ellipsis] = '...')
7231 * @returns String
7232 * @short Truncates a string without splitting up words.
7233 * @extra [from] can be %'right'%, %'left'%, or %'middle'%. If the string is shorter than <length>, [ellipsis] will not be added.
7234 * @example
7235 *
7236 * 'here we go'.truncateOnWord(5) -> 'here...'
7237 * 'here we go'.truncateOnWord(5, 'left') -> '...we go'
7238 *
7239 ***/
7240 'truncateOnWord': function(length, from, ellipsis) {
7241 return truncateString(this, length, from, ellipsis, true);
7242 },
7243
7244 /***
7245 * @method pad[Side](<num> = null, [padding] = ' ')
7246 * @returns String
7247 * @short Pads the string out with [padding] to be exactly <num> characters.
7248 *
7249 * @set
7250 * pad
7251 * padLeft
7252 * padRight
7253 *
7254 * @example
7255 *
7256 * 'wasabi'.pad(8) -> ' wasabi '
7257 * 'wasabi'.padLeft(8) -> ' wasabi'
7258 * 'wasabi'.padRight(8) -> 'wasabi '
7259 * 'wasabi'.padRight(8, '-') -> 'wasabi--'
7260 *
7261 ***/
7262 'pad': function(num, padding) {
7263 var half, front, back;
7264 num = checkRepeatRange(num);
7265 half = max(0, num - this.length) / 2;
7266 front = floor(half);
7267 back = ceil(half);
7268 return padString(front, padding) + this + padString(back, padding);
7269 },
7270
7271 'padLeft': function(num, padding) {
7272 num = checkRepeatRange(num);
7273 return padString(max(0, num - this.length), padding) + this;
7274 },
7275
7276 'padRight': function(num, padding) {
7277 num = checkRepeatRange(num);
7278 return this + padString(max(0, num - this.length), padding);
7279 },
7280
7281 /***
7282 * @method first([n] = 1)
7283 * @returns String
7284 * @short Returns the first [n] characters of the string.
7285 * @example
7286 *
7287 * 'lucky charms'.first() -> 'l'
7288 * 'lucky charms'.first(3) -> 'luc'
7289 *
7290 ***/
7291 'first': function(num) {
7292 if(isUndefined(num)) num = 1;
7293 return this.substr(0, num);
7294 },
7295
7296 /***
7297 * @method last([n] = 1)
7298 * @returns String
7299 * @short Returns the last [n] characters of the string.
7300 * @example
7301 *
7302 * 'lucky charms'.last() -> 's'
7303 * 'lucky charms'.last(3) -> 'rms'
7304 *
7305 ***/
7306 'last': function(num) {
7307 if(isUndefined(num)) num = 1;
7308 var start = this.length - num < 0 ? 0 : this.length - num;
7309 return this.substr(start);
7310 },
7311
7312 /***
7313 * @method toNumber([base] = 10)
7314 * @returns Number
7315 * @short Converts the string into a number.
7316 * @extra Any value with a "." fill be converted to a floating point value, otherwise an integer.
7317 * @example
7318 *
7319 * '153'.toNumber() -> 153
7320 * '12,000'.toNumber() -> 12000
7321 * '10px'.toNumber() -> 10
7322 * 'ff'.toNumber(16) -> 255
7323 *
7324 ***/
7325 'toNumber': function(base) {
7326 return stringToNumber(this, base);
7327 },
7328
7329 /***
7330 * @method capitalize([all] = false)
7331 * @returns String
7332 * @short Capitalizes the first character in the string and downcases all ot her letters.
7333 * @extra If [all] is true, all words in the string will be capitalized.
7334 * @example
7335 *
7336 * 'hello'.capitalize() -> 'Hello'
7337 * 'hello kitty'.capitalize() -> 'Hello kitty'
7338 * 'hello kitty'.capitalize(true) -> 'Hello Kitty'
7339 *
7340 *
7341 ***/
7342 'capitalize': function(all) {
7343 var lastResponded;
7344 return this.toLowerCase().replace(all ? /[^']/g : /^\S/, function(lower) {
7345 var upper = lower.toUpperCase(), result;
7346 result = lastResponded ? lower : upper;
7347 lastResponded = upper !== lower;
7348 return result;
7349 });
7350 },
7351
7352 /***
7353 * @method assign(<obj1>, <obj2>, ...)
7354 * @returns String
7355 * @short Assigns variables to tokens in a string, demarcated with `{}`.
7356 * @extra If an object is passed, it's properties can be assigned using the object's keys (i.e. {name}). 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 (original objects are unaffected).
7357 * @example
7358 *
7359 * 'Welcome, Mr. {name}.'.assign({ name: 'Franklin' }) -> 'Welcome, Mr. Franklin.'
7360 * 'You are {1} years old today.'.assign(14) -> 'You are 14 ye ars old today.'
7361 * '{n} and {r}'.assign({ n: 'Cheech' }, { r: 'Chong' }) -> 'Cheech and Ch ong'
7362 *
7363 ***/
7364 'assign': function() {
7365 var assign = {};
7366 flattenedArgs(arguments, function(a, i) {
7367 if(isObjectType(a)) {
7368 simpleMerge(assign, a);
7369 } else {
7370 assign[i + 1] = a;
7371 }
7372 });
7373 return this.replace(/\{([^{]+?)\}/g, function(m, key) {
7374 return hasOwnProperty(assign, key) ? assign[key] : m;
7375 });
7376 }
7377
7378 });
7379
7380
7381 // Aliases
7382
7383 extend(string, true, true, {
7384
7385 /***
7386 * @method insert()
7387 * @alias add
7388 *
7389 ***/
7390 'insert': string.prototype.add
7391 });
7392
7393 buildBase64('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= ');
7394
7395
7396 /***
7397 *
7398 * @package Inflections
7399 * @dependency string
7400 * @description Pluralization similar to ActiveSupport including uncountable w ords and acronyms. Humanized and URL-friendly strings.
7401 *
7402 ***/
7403
7404 /***
7405 * String module
7406 *
7407 ***/
7408
7409
7410 var plurals = [],
7411 singulars = [],
7412 uncountables = [],
7413 humans = [],
7414 acronyms = {},
7415 Downcased,
7416 Inflector;
7417
7418 function removeFromArray(arr, find) {
7419 var index = arr.indexOf(find);
7420 if(index > -1) {
7421 arr.splice(index, 1);
7422 }
7423 }
7424
7425 function removeFromUncountablesAndAddTo(arr, rule, replacement) {
7426 if(isString(rule)) {
7427 removeFromArray(uncountables, rule);
7428 }
7429 removeFromArray(uncountables, replacement);
7430 arr.unshift({ rule: rule, replacement: replacement })
7431 }
7432
7433 function paramMatchesType(param, type) {
7434 return param == type || param == 'all' || !param;
7435 }
7436
7437 function isUncountable(word) {
7438 return uncountables.some(function(uncountable) {
7439 return new regexp('\\b' + uncountable + '$', 'i').test(word);
7440 });
7441 }
7442
7443 function inflect(word, pluralize) {
7444 word = isString(word) ? word.toString() : '';
7445 if(word.isBlank() || isUncountable(word)) {
7446 return word;
7447 } else {
7448 return runReplacements(word, pluralize ? plurals : singulars);
7449 }
7450 }
7451
7452 function runReplacements(word, table) {
7453 iterateOverObject(table, function(i, inflection) {
7454 if(word.match(inflection.rule)) {
7455 word = word.replace(inflection.rule, inflection.replacement);
7456 return false;
7457 }
7458 });
7459 return word;
7460 }
7461
7462 function capitalize(word) {
7463 return word.replace(/^\W*[a-z]/, function(w){
7464 return w.toUpperCase();
7465 });
7466 }
7467
7468 Inflector = {
7469
7470 /*
7471 * Specifies a new acronym. An acronym must be specified as it will appear i n a camelized string. An underscore
7472 * string that contains the acronym will retain the acronym when passed to % camelize%, %humanize%, or %titleize%.
7473 * A camelized string that contains the acronym will maintain the acronym wh en titleized or humanized, and will
7474 * convert the acronym into a non-delimited single lowercase word when passe d to String#underscore.
7475 *
7476 * Examples:
7477 * String.Inflector.acronym('HTML')
7478 * 'html'.titleize() -> 'HTML'
7479 * 'html'.camelize() -> 'HTML'
7480 * 'MyHTML'.underscore() -> 'my_html'
7481 *
7482 * The acronym, however, must occur as a delimited unit and not be part of a nother word for conversions to recognize it:
7483 *
7484 * String.Inflector.acronym('HTTP')
7485 * 'my_http_delimited'.camelize() -> 'MyHTTPDelimited'
7486 * 'https'.camelize() -> 'Https', not 'HTTPs'
7487 * 'HTTPS'.underscore() -> 'http_s', not 'https'
7488 *
7489 * String.Inflector.acronym('HTTPS')
7490 * 'https'.camelize() -> 'HTTPS'
7491 * 'HTTPS'.underscore() -> 'https'
7492 *
7493 * Note: Acronyms that are passed to %pluralize% will no longer be recognize d, since the acronym will not occur as
7494 * a delimited unit in the pluralized result. To work around this, you must specify the pluralized form as an
7495 * acronym as well:
7496 *
7497 * String.Inflector.acronym('API')
7498 * 'api'.pluralize().camelize() -> 'Apis'
7499 *
7500 * String.Inflector.acronym('APIs')
7501 * 'api'.pluralize().camelize() -> 'APIs'
7502 *
7503 * %acronym% may be used to specify any word that contains an acronym or oth erwise needs to maintain a non-standard
7504 * capitalization. The only restriction is that the word must begin with a c apital letter.
7505 *
7506 * Examples:
7507 * String.Inflector.acronym('RESTful')
7508 * 'RESTful'.underscore() -> 'restful'
7509 * 'RESTfulController'.underscore() -> 'restful_controller'
7510 * 'RESTfulController'.titleize() -> 'RESTful Controller'
7511 * 'restful'.camelize() -> 'RESTful'
7512 * 'restful_controller'.camelize() -> 'RESTfulController'
7513 *
7514 * String.Inflector.acronym('McDonald')
7515 * 'McDonald'.underscore() -> 'mcdonald'
7516 * 'mcdonald'.camelize() -> 'McDonald'
7517 */
7518 'acronym': function(word) {
7519 acronyms[word.toLowerCase()] = word;
7520 var all = object.keys(acronyms).map(function(key) {
7521 return acronyms[key];
7522 });
7523 Inflector.acronymRegExp = regexp(all.join('|'), 'g');
7524 },
7525
7526 /*
7527 * Specifies a new pluralization rule and its replacement. The rule can eith er be a string or a regular expression.
7528 * The replacement should always be a string that may include references to the matched data from the rule.
7529 */
7530 'plural': function(rule, replacement) {
7531 removeFromUncountablesAndAddTo(plurals, rule, replacement);
7532 },
7533
7534 /*
7535 * Specifies a new singularization rule and its replacement. The rule can ei ther be a string or a regular expression.
7536 * The replacement should always be a string that may include references to the matched data from the rule.
7537 */
7538 'singular': function(rule, replacement) {
7539 removeFromUncountablesAndAddTo(singulars, rule, replacement);
7540 },
7541
7542 /*
7543 * Specifies a new irregular that applies to both pluralization and singular ization at the same time. This can only be used
7544 * for strings, not regular expressions. You simply pass the irregular in si ngular and plural form.
7545 *
7546 * Examples:
7547 * String.Inflector.irregular('octopus', 'octopi')
7548 * String.Inflector.irregular('person', 'people')
7549 */
7550 'irregular': function(singular, plural) {
7551 var singularFirst = singular.first(),
7552 singularRest = singular.from(1),
7553 pluralFirst = plural.first(),
7554 pluralRest = plural.from(1),
7555 pluralFirstUpper = pluralFirst.toUpperCase(),
7556 pluralFirstLower = pluralFirst.toLowerCase(),
7557 singularFirstUpper = singularFirst.toUpperCase(),
7558 singularFirstLower = singularFirst.toLowerCase();
7559 removeFromArray(uncountables, singular);
7560 removeFromArray(uncountables, plural);
7561 if(singularFirstUpper == pluralFirstUpper) {
7562 Inflector.plural(new regexp('({1}){2}$'.assign(singularFirst, singularRe st), 'i'), '$1' + pluralRest);
7563 Inflector.plural(new regexp('({1}){2}$'.assign(pluralFirst, pluralRest), 'i'), '$1' + pluralRest);
7564 Inflector.singular(new regexp('({1}){2}$'.assign(pluralFirst, pluralRest ), 'i'), '$1' + singularRest);
7565 } else {
7566 Inflector.plural(new regexp('{1}{2}$'.assign(singularFirstUpper, singula rRest)), pluralFirstUpper + pluralRest);
7567 Inflector.plural(new regexp('{1}{2}$'.assign(singularFirstLower, singula rRest)), pluralFirstLower + pluralRest);
7568 Inflector.plural(new regexp('{1}{2}$'.assign(pluralFirstUpper, pluralRes t)), pluralFirstUpper + pluralRest);
7569 Inflector.plural(new regexp('{1}{2}$'.assign(pluralFirstLower, pluralRes t)), pluralFirstLower + pluralRest);
7570 Inflector.singular(new regexp('{1}{2}$'.assign(pluralFirstUpper, pluralR est)), singularFirstUpper + singularRest);
7571 Inflector.singular(new regexp('{1}{2}$'.assign(pluralFirstLower, pluralR est)), singularFirstLower + singularRest);
7572 }
7573 },
7574
7575 /*
7576 * Add uncountable words that shouldn't be attempted inflected.
7577 *
7578 * Examples:
7579 * String.Inflector.uncountable('money')
7580 * String.Inflector.uncountable('money', 'information')
7581 * String.Inflector.uncountable(['money', 'information', 'rice'])
7582 */
7583 'uncountable': function(first) {
7584 var add = array.isArray(first) ? first : multiArgs(arguments);
7585 uncountables = uncountables.concat(add);
7586 },
7587
7588 /*
7589 * Specifies a humanized form of a string by a regular expression rule or by a string mapping.
7590 * When using a regular expression based replacement, the normal humanize fo rmatting is called after the replacement.
7591 * When a string is used, the human form should be specified as desired (exa mple: 'The name', not 'the_name')
7592 *
7593 * Examples:
7594 * String.Inflector.human(/_cnt$/i, '_count')
7595 * String.Inflector.human('legacy_col_person_name', 'Name')
7596 */
7597 'human': function(rule, replacement) {
7598 humans.unshift({ rule: rule, replacement: replacement })
7599 },
7600
7601
7602 /*
7603 * Clears the loaded inflections within a given scope (default is 'all').
7604 * Options are: 'all', 'plurals', 'singulars', 'uncountables', 'humans'.
7605 *
7606 * Examples:
7607 * String.Inflector.clear('all')
7608 * String.Inflector.clear('plurals')
7609 */
7610 'clear': function(type) {
7611 if(paramMatchesType(type, 'singulars')) singulars = [];
7612 if(paramMatchesType(type, 'plurals')) plurals = [];
7613 if(paramMatchesType(type, 'uncountables')) uncountables = [];
7614 if(paramMatchesType(type, 'humans')) humans = [];
7615 if(paramMatchesType(type, 'acronyms')) acronyms = {};
7616 }
7617
7618 };
7619
7620 Downcased = [
7621 'and', 'or', 'nor', 'a', 'an', 'the', 'so', 'but', 'to', 'of', 'at',
7622 'by', 'from', 'into', 'on', 'onto', 'off', 'out', 'in', 'over',
7623 'with', 'for'
7624 ];
7625
7626 Inflector.plural(/$/, 's');
7627 Inflector.plural(/s$/gi, 's');
7628 Inflector.plural(/(ax|test)is$/gi, '$1es');
7629 Inflector.plural(/(octop|vir|fung|foc|radi|alumn)(i|us)$/gi, '$1i');
7630 Inflector.plural(/(census|alias|status)$/gi, '$1es');
7631 Inflector.plural(/(bu)s$/gi, '$1ses');
7632 Inflector.plural(/(buffal|tomat)o$/gi, '$1oes');
7633 Inflector.plural(/([ti])um$/gi, '$1a');
7634 Inflector.plural(/([ti])a$/gi, '$1a');
7635 Inflector.plural(/sis$/gi, 'ses');
7636 Inflector.plural(/f+e?$/gi, 'ves');
7637 Inflector.plural(/(cuff|roof)$/gi, '$1s');
7638 Inflector.plural(/([ht]ive)$/gi, '$1s');
7639 Inflector.plural(/([^aeiouy]o)$/gi, '$1es');
7640 Inflector.plural(/([^aeiouy]|qu)y$/gi, '$1ies');
7641 Inflector.plural(/(x|ch|ss|sh)$/gi, '$1es');
7642 Inflector.plural(/(matr|vert|ind)(?:ix|ex)$/gi, '$1ices');
7643 Inflector.plural(/([ml])ouse$/gi, '$1ice');
7644 Inflector.plural(/([ml])ice$/gi, '$1ice');
7645 Inflector.plural(/^(ox)$/gi, '$1en');
7646 Inflector.plural(/^(oxen)$/gi, '$1');
7647 Inflector.plural(/(quiz)$/gi, '$1zes');
7648 Inflector.plural(/(phot|cant|hom|zer|pian|portic|pr|quart|kimon)o$/gi, '$1os') ;
7649 Inflector.plural(/(craft)$/gi, '$1');
7650 Inflector.plural(/([ft])[eo]{2}(th?)$/gi, '$1ee$2');
7651
7652 Inflector.singular(/s$/gi, '');
7653 Inflector.singular(/([pst][aiu]s)$/gi, '$1');
7654 Inflector.singular(/([aeiouy])ss$/gi, '$1ss');
7655 Inflector.singular(/(n)ews$/gi, '$1ews');
7656 Inflector.singular(/([ti])a$/gi, '$1um');
7657 Inflector.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)s es$/gi, '$1$2sis');
7658 Inflector.singular(/(^analy)ses$/gi, '$1sis');
7659 Inflector.singular(/(i)(f|ves)$/i, '$1fe');
7660 Inflector.singular(/([aeolr]f?)(f|ves)$/i, '$1f');
7661 Inflector.singular(/([ht]ive)s$/gi, '$1');
7662 Inflector.singular(/([^aeiouy]|qu)ies$/gi, '$1y');
7663 Inflector.singular(/(s)eries$/gi, '$1eries');
7664 Inflector.singular(/(m)ovies$/gi, '$1ovie');
7665 Inflector.singular(/(x|ch|ss|sh)es$/gi, '$1');
7666 Inflector.singular(/([ml])(ous|ic)e$/gi, '$1ouse');
7667 Inflector.singular(/(bus)(es)?$/gi, '$1');
7668 Inflector.singular(/(o)es$/gi, '$1');
7669 Inflector.singular(/(shoe)s?$/gi, '$1');
7670 Inflector.singular(/(cris|ax|test)[ie]s$/gi, '$1is');
7671 Inflector.singular(/(octop|vir|fung|foc|radi|alumn)(i|us)$/gi, '$1us');
7672 Inflector.singular(/(census|alias|status)(es)?$/gi, '$1');
7673 Inflector.singular(/^(ox)(en)?/gi, '$1');
7674 Inflector.singular(/(vert|ind)(ex|ices)$/gi, '$1ex');
7675 Inflector.singular(/(matr)(ix|ices)$/gi, '$1ix');
7676 Inflector.singular(/(quiz)(zes)?$/gi, '$1');
7677 Inflector.singular(/(database)s?$/gi, '$1');
7678 Inflector.singular(/ee(th?)$/gi, 'oo$1');
7679
7680 Inflector.irregular('person', 'people');
7681 Inflector.irregular('man', 'men');
7682 Inflector.irregular('child', 'children');
7683 Inflector.irregular('sex', 'sexes');
7684 Inflector.irregular('move', 'moves');
7685 Inflector.irregular('save', 'saves');
7686 Inflector.irregular('cow', 'kine');
7687 Inflector.irregular('goose', 'geese');
7688 Inflector.irregular('zombie', 'zombies');
7689
7690 Inflector.uncountable('equipment,information,rice,money,species,series,fish,sh eep,jeans'.split(','));
7691
7692
7693 extend(string, true, true, {
7694
7695 /***
7696 * @method pluralize()
7697 * @returns String
7698 * @short Returns the plural form of the word in the string.
7699 * @example
7700 *
7701 * 'post'.pluralize() -> 'posts'
7702 * 'octopus'.pluralize() -> 'octopi'
7703 * 'sheep'.pluralize() -> 'sheep'
7704 * 'words'.pluralize() -> 'words'
7705 * 'CamelOctopus'.pluralize() -> 'CamelOctopi'
7706 *
7707 ***/
7708 'pluralize': function() {
7709 return inflect(this, true);
7710 },
7711
7712 /***
7713 * @method singularize()
7714 * @returns String
7715 * @short The reverse of String#pluralize. Returns the singular form of a wo rd in a string.
7716 * @example
7717 *
7718 * 'posts'.singularize() -> 'post'
7719 * 'octopi'.singularize() -> 'octopus'
7720 * 'sheep'.singularize() -> 'sheep'
7721 * 'word'.singularize() -> 'word'
7722 * 'CamelOctopi'.singularize() -> 'CamelOctopus'
7723 *
7724 ***/
7725 'singularize': function() {
7726 return inflect(this, false);
7727 },
7728
7729 /***
7730 * @method humanize()
7731 * @returns String
7732 * @short Creates a human readable string.
7733 * @extra Capitalizes the first word and turns underscores into spaces and s trips a trailing '_id', if any. Like String#titleize, this is meant for creating pretty output.
7734 * @example
7735 *
7736 * 'employee_salary'.humanize() -> 'Employee salary'
7737 * 'author_id'.humanize() -> 'Author'
7738 *
7739 ***/
7740 'humanize': function() {
7741 var str = runReplacements(this, humans), acronym;
7742 str = str.replace(/_id$/g, '');
7743 str = str.replace(/(_)?([a-z\d]*)/gi, function(match, _, word){
7744 acronym = hasOwnProperty(acronyms, word) ? acronyms[word] : null;
7745 return (_ ? ' ' : '') + (acronym || word.toLowerCase());
7746 });
7747 return capitalize(str);
7748 },
7749
7750 /***
7751 * @method titleize()
7752 * @returns String
7753 * @short Creates a title version of the string.
7754 * @extra Capitalizes all the words and replaces some characters in the stri ng to create a nicer looking title. String#titleize is meant for creating pretty output.
7755 * @example
7756 *
7757 * 'man from the boondocks'.titleize() -> 'Man from the Boondocks'
7758 * 'x-men: the last stand'.titleize() -> 'X Men: The Last Stand'
7759 * 'TheManWithoutAPast'.titleize() -> 'The Man Without a Past'
7760 * 'raiders_of_the_lost_ark'.titleize() -> 'Raiders of the Lost Ark'
7761 *
7762 ***/
7763 'titleize': function() {
7764 var fullStopPunctuation = /[.:;!]$/, hasPunctuation, lastHadPunctuation, i sFirstOrLast;
7765 return this.spacify().humanize().words(function(word, index, words) {
7766 hasPunctuation = fullStopPunctuation.test(word);
7767 isFirstOrLast = index == 0 || index == words.length - 1 || hasPunctuatio n || lastHadPunctuation;
7768 lastHadPunctuation = hasPunctuation;
7769 if(isFirstOrLast || Downcased.indexOf(word) === -1) {
7770 return capitalize(word);
7771 } else {
7772 return word;
7773 }
7774 }).join(' ');
7775 },
7776
7777 /***
7778 * @method parameterize()
7779 * @returns String
7780 * @short Replaces special characters in a string so that it may be used as part of a pretty URL.
7781 * @example
7782 *
7783 * 'hell, no!'.parameterize() -> 'hell-no'
7784 *
7785 ***/
7786 'parameterize': function(separator) {
7787 var str = this;
7788 if(separator === undefined) separator = '-';
7789 if(str.normalize) {
7790 str = str.normalize();
7791 }
7792 str = str.replace(/[^a-z0-9\-_]+/gi, separator)
7793 if(separator) {
7794 str = str.replace(new regexp('^{sep}+|{sep}+$|({sep}){sep}+'.assign({ 's ep': escapeRegExp(separator) }), 'g'), '$1');
7795 }
7796 return encodeURI(str.toLowerCase());
7797 }
7798
7799 });
7800
7801 string.Inflector = Inflector;
7802 string.Inflector.acronyms = acronyms;
7803
7804 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698