1/* 2* Sugar v2.0.2 3* 4* Freely distributable and licensed under the MIT-style license. 5* Copyright (c) Andrew Plummer 6* https://sugarjs.com/ 7* 8* ---------------------------- */ 9(function() { 10 'use strict'; 11 12 /*** 13 * @module Core 14 * @description Core functionality including the ability to define methods and 15 * extend onto natives. 16 * 17 ***/ 18 19 // The global to export. 20 var Sugar; 21 22 // The name of Sugar in the global namespace. 23 var SUGAR_GLOBAL = 'Sugar'; 24 25 // Natives available on initialization. Letting Object go first to ensure its 26 // global is set by the time the rest are checking for chainable Object methods. 27 var NATIVE_NAMES = 'Object Number String Array Date RegExp Function'; 28 29 // Static method flag 30 var STATIC = 0x1; 31 32 // Instance method flag 33 var INSTANCE = 0x2; 34 35 // IE8 has a broken defineProperty but no defineProperties so this saves a try/catch. 36 var PROPERTY_DESCRIPTOR_SUPPORT = !!(Object.defineProperty && Object.defineProperties); 37 38 // The global context. Rhino uses a different "global" keyword so 39 // do an extra check to be sure that it's actually the global context. 40 var globalContext = typeof global !== 'undefined' && global.Object === Object ? global : this; 41 42 // Is the environment node? 43 var hasExports = typeof module !== 'undefined' && module.exports; 44 45 // Whether object instance methods can be mapped to the prototype. 46 var allowObjectPrototype = false; 47 48 // A map from Array to SugarArray. 49 var namespacesByName = {}; 50 51 // A map from [object Object] to namespace. 52 var namespacesByClassString = {}; 53 54 // Defining properties. 55 var defineProperty = PROPERTY_DESCRIPTOR_SUPPORT ? Object.defineProperty : definePropertyShim; 56 57 // A default chainable class for unknown types. 58 var DefaultChainable = getNewChainableClass('Chainable'); 59 60 61 // Global methods 62 63 function setupGlobal() { 64 Sugar = globalContext[SUGAR_GLOBAL]; 65 if (Sugar) { 66 // Reuse already defined Sugar global object. 67 return; 68 } 69 Sugar = function(arg) { 70 forEachProperty(Sugar, function(sugarNamespace, name) { 71 // Although only the only enumerable properties on the global 72 // object are Sugar namespaces, environments that can't set 73 // non-enumerable properties will step through the utility methods 74 // as well here, so use this check to only allow true namespaces. 75 if (hasOwn(namespacesByName, name)) { 76 sugarNamespace.extend(arg); 77 } 78 }); 79 return Sugar; 80 }; 81 if (hasExports) { 82 module.exports = Sugar; 83 } else { 84 try { 85 globalContext[SUGAR_GLOBAL] = Sugar; 86 } catch (e) { 87 // Contexts such as QML have a read-only global context. 88 } 89 } 90 forEachProperty(NATIVE_NAMES.split(' '), function(name) { 91 createNamespace(name); 92 }); 93 setGlobalProperties(); 94 } 95 96 /*** 97 * @method createNamespace(name) 98 * @returns SugarNamespace 99 * @namespace Sugar 100 * @short Creates a new Sugar namespace. 101 * @extra This method is for plugin developers who want to define methods to be 102 * used with natives that Sugar does not handle by default. The new 103 * namespace will appear on the `Sugar` global with all the methods of 104 * normal namespaces, including the ability to define new methods. When 105 * extended, any defined methods will be mapped to `name` in the global 106 * context. 107 * 108 * @example 109 * 110 * Sugar.createNamespace('Boolean'); 111 * 112 * @param {string} name - The namespace name. 113 * 114 ***/ 115 function createNamespace(name) { 116 117 // Is the current namespace Object? 118 var isObject = name === 'Object'; 119 120 // A Sugar namespace is also a chainable class: Sugar.Array, etc. 121 var sugarNamespace = getNewChainableClass(name, true); 122 123 /*** 124 * @method extend([opts]) 125 * @returns Sugar 126 * @namespace Sugar 127 * @short Extends Sugar defined methods onto natives. 128 * @extra This method can be called on individual namespaces like 129 * `Sugar.Array` or on the `Sugar` global itself, in which case 130 * [opts] will be forwarded to each `extend` call. For more, 131 * see `extending`. 132 * 133 * @options 134 *
135 * methods An array of method names to explicitly extend. 136 * 137 * except An array of method names or global namespaces (`Array`, 138 * `String`) to explicitly exclude. Namespaces should be the 139 * actual global objects, not strings. 140 * 141 * namespaces An array of global namespaces (`Array`, `String`) to 142 * explicitly extend. Namespaces should be the actual 143 * global objects, not strings. 144 * 145 * enhance A shortcut to disallow all "enhance" flags at once 146 * (flags listed below). For more, see `enhanced methods`. 147 * Default is `true`. 148 * 149 * enhanceString A boolean allowing String enhancements. Default is `true`. 150 * 151 * enhanceArray A boolean allowing Array enhancements. Default is `true`. 152 * 153 * objectPrototype A boolean allowing Sugar to extend Object.prototype 154 * with instance methods. This option is off by default 155 * and should generally not be used except with caution. 156 * For more, see `object methods`. 157 * 158 * @example 159 * 160 * Sugar.Array.extend(); 161 * Sugar.extend(); 162 * 163 * @option {Array<string>} [methods] 164 * @option {Array<string|NativeConstructor>} [except] 165 * @option {Array<NativeConstructor>} [namespaces] 166 * @option {boolean} [enhance] 167 * @option {boolean} [enhanceString] 168 * @option {boolean} [enhanceArray] 169 * @option {boolean} [objectPrototype] 170 * @param {ExtendOptions} [opts] 171 * 172 *** 173 * @method extend([opts]) 174 * @returns SugarNamespace 175 * @namespace SugarNamespace 176 * @short Extends Sugar defined methods for a specific namespace onto natives. 177 * @param {ExtendOptions} [opts] 178 * 179 ***/ 180 var extend = function(opts) { 181 182 var nativeClass = globalContext[name], nativeProto = nativeClass.prototype; 183 var staticMethods = {}, instanceMethods = {}, methodsByName; 184 185 function objectRestricted(name, target) { 186 return isObject && target === nativeProto && 187 (!allowObjectPrototype || name === 'get' || name === 'set'); 188 } 189 190 function arrayOptionExists(field, val) { 191 var arr = opts[field]; 192 if (arr) { 193 for (var i = 0, el; el = arr[i]; i++) { 194 if (el === val) { 195 return true; 196 } 197 } 198 } 199 return false; 200 } 201 202 function arrayOptionExcludes(field, val) { 203 return opts[field] && !arrayOptionExists(field, val); 204 } 205 206 function disallowedByFlags(methodName, target, flags) { 207 // Disallowing methods by flag currently only applies if methods already 208 // exist to avoid enhancing native methods, as aliases should still be 209 // extended (i.e. Array#all should still be extended even if Array#every 210 // is being disallowed by a flag). 211 if (!target[methodName] || !flags) { 212 return false; 213 } 214 for (var i = 0; i < flags.length; i++) { 215 if (opts[flags[i]] === false) { 216 return true; 217 } 218 } 219 } 220 221 function namespaceIsExcepted() { 222 return arrayOptionExists('except', nativeClass) || 223 arrayOptionExcludes('namespaces', nativeClass); 224 } 225 226 function methodIsExcepted(methodName) { 227 return arrayOptionExists('except', methodName); 228 } 229 230 function canExtend(methodName, method, target) { 231 return !objectRestricted(methodName, target) && 232 !disallowedByFlags(methodName, target, method.flags) && 233 !methodIsExcepted(methodName); 234 } 235 236 opts = opts || {}; 237 methodsByName = opts.methods; 238 239 if (namespaceIsExcepted()) { 240 return; 241 } else if (isObject && typeof opts.objectPrototype === 'boolean') { 242 // Store "objectPrototype" flag for future reference. 243 allowObjectPrototype = opts.objectPrototype; 244 } 245
246 forEachProperty(methodsByName || sugarNamespace, function(method, methodName) { 247 if (methodsByName) { 248 // If we have method names passed in an array, 249 // then we need to flip the key and value here 250 // and find the method in the Sugar namespace. 251 methodName = method; 252 method = sugarNamespace[methodName]; 253 } 254 if (hasOwn(method, 'instance') && canExtend(methodName, method, nativeProto)) { 255 instanceMethods[methodName] = method.instance; 256 } 257 if (hasOwn(method, 'static') && canExtend(methodName, method, nativeClass)) { 258 staticMethods[methodName] = method; 259 } 260 }); 261 262 // Accessing the extend target each time instead of holding a reference as 263 // it may have been overwritten (for example Date by Sinon). Also need to 264 // access through the global to allow extension of user-defined namespaces. 265 extendNative(nativeClass, staticMethods); 266 extendNative(nativeProto, instanceMethods); 267 268 if (!methodsByName) { 269 // If there are no method names passed, then 270 // all methods in the namespace will be extended 271 // to the native. This includes all future defined 272 // methods, so add a flag here to check later. 273 setProperty(sugarNamespace, 'active', true); 274 } 275 return sugarNamespace; 276 }; 277 278 function defineWithOptionCollect(methodName, instance, args) { 279 setProperty(sugarNamespace, methodName, function(arg1, arg2, arg3) { 280 var opts = collectDefineOptions(arg1, arg2, arg3); 281 defineMethods(sugarNamespace, opts.methods, instance, args, opts.last); 282 return sugarNamespace; 283 }); 284 } 285 286 /*** 287 * @method defineStatic(methods) 288 * @returns SugarNamespace 289 * @namespace SugarNamespace 290 * @short Defines static methods on the namespace that can later be extended 291 * onto the native globals. 292 * @extra Accepts either a single object mapping names to functions, or name 293 * and function as two arguments. If `extend` was previously called 294 * with no arguments, the method will be immediately mapped to its 295 * native when defined. 296 * 297 * @example 298 * 299 * Sugar.Number.defineStatic({ 300 * isOdd: function (num) { 301 * return num % 2 === 1; 302 * } 303 * }); 304 * 305 * @signature defineStatic(methodName, methodFn) 306 * @param {Object} methods - Methods to be defined. 307 * @param {string} methodName - Name of a single method to be defined. 308 * @param {Function} methodFn - Function body of a single method to be defined. 309 ***/ 310 defineWithOptionCollect('defineStatic', STATIC); 311 312 /*** 313 * @method defineInstance(methods) 314 * @returns SugarNamespace 315 * @namespace SugarNamespace 316 * @short Defines methods on the namespace that can later be extended as 317 * instance methods onto the native prototype. 318 * @extra Accepts either a single object mapping names to functions, or name 319 * and function as two arguments. All functions should accept the 320 * native for which they are mapped as their first argument, and should 321 * never refer to `this`. If `extend` was previously called with no 322 * arguments, the method will be immediately mapped to its native when 323 * defined. 324 * 325 * Methods cannot accept more than 4 arguments in addition to the 326 * native (5 arguments total). Any additional arguments will not be 327 * mapped. If the method needs to accept unlimited arguments, use 328 * `defineInstanceWithArguments`. Otherwise if more options are 329 * required, use an options object instead. 330 * 331 * @example 332 * 333 * Sugar.Number.defineInstance({ 334 * square: function (num) { 335 * return num * num; 336 * } 337 * }); 338 * 339 * @signature defineInstance(methodName, methodFn) 340 * @param {Object} methods - Methods to be defined. 341 * @param {string} methodName - Name of a single method to be defined. 342 * @param {Function} methodFn - Function body of a single method to be defined. 343 ***/ 344 defineWithOptionCollect('defineInstance', INSTANCE); 345 346 /*** 347 * @method defineInstanceAndStatic(methods) 348 * @returns SugarNamespace 349 * @namespace SugarNamespace
350 * @short A shortcut to define both static and instance methods on the namespace. 351 * @extra This method is intended for use with `Object` instance methods. Sugar 352 * will not map any methods to `Object.prototype` by default, so defining 353 * instance methods as static helps facilitate their proper use. 354 * 355 * @example 356 * 357 * Sugar.Object.defineInstanceAndStatic({ 358 * isAwesome: function (obj) { 359 * // check if obj is awesome! 360 * } 361 * }); 362 * 363 * @signature defineInstanceAndStatic(methodName, methodFn) 364 * @param {Object} methods - Methods to be defined. 365 * @param {string} methodName - Name of a single method to be defined. 366 * @param {Function} methodFn - Function body of a single method to be defined. 367 ***/ 368 defineWithOptionCollect('defineInstanceAndStatic', INSTANCE | STATIC); 369 370 371 /*** 372 * @method defineStaticWithArguments(methods) 373 * @returns SugarNamespace 374 * @namespace SugarNamespace 375 * @short Defines static methods that collect arguments. 376 * @extra This method is identical to `defineStatic`, except that when defined 377 * methods are called, they will collect any arguments past `n - 1`, 378 * where `n` is the number of arguments that the method accepts. 379 * Collected arguments will be passed to the method in an array 380 * as the last argument defined on the function. 381 * 382 * @example 383 * 384 * Sugar.Number.defineStaticWithArguments({ 385 * addAll: function (num, args) { 386 * for (var i = 0; i < args.length; i++) { 387 * num += args[i]; 388 * } 389 * return num; 390 * } 391 * }); 392 * 393 * @signature defineStaticWithArguments(methodName, methodFn) 394 * @param {Object} methods - Methods to be defined. 395 * @param {string} methodName - Name of a single method to be defined. 396 * @param {Function} methodFn - Function body of a single method to be defined. 397 ***/ 398 defineWithOptionCollect('defineStaticWithArguments', STATIC, true); 399 400 /*** 401 * @method defineInstanceWithArguments(methods) 402 * @returns SugarNamespace 403 * @namespace SugarNamespace 404 * @short Defines instance methods that collect arguments. 405 * @extra This method is identical to `defineInstance`, except that when 406 * defined methods are called, they will collect any arguments past 407 * `n - 1`, where `n` is the number of arguments that the method 408 * accepts. Collected arguments will be passed to the method as the 409 * last argument defined on the function. 410 * 411 * @example 412 * 413 * Sugar.Number.defineInstanceWithArguments({ 414 * addAll: function (num, args) { 415 * for (var i = 0; i < args.length; i++) { 416 * num += args[i]; 417 * } 418 * return num; 419 * } 420 * }); 421 * 422 * @signature defineInstanceWithArguments(methodName, methodFn) 423 * @param {Object} methods - Methods to be defined. 424 * @param {string} methodName - Name of a single method to be defined. 425 * @param {Function} methodFn - Function body of a single method to be defined. 426 ***/ 427 defineWithOptionCollect('defineInstanceWithArguments', INSTANCE, true); 428 429 /*** 430 * @method defineStaticPolyfill(methods) 431 * @returns SugarNamespace 432 * @namespace SugarNamespace 433 * @short Defines static methods that are mapped onto the native if they do 434 * not already exist. 435 * @extra Intended only for use creating polyfills that follow the ECMAScript 436 * spec. Accepts either a single object mapping names to functions, or 437 * name and function as two arguments. 438 * 439 * @example 440 * 441 * Sugar.Object.defineStaticPolyfill({ 442 * keys: function (obj) { 443 * // get keys! 444 * } 445 * }); 446 * 447 * @signature defineStaticPolyfill(methodName, methodFn) 448 * @param {Object} methods - Methods to be defined. 449 * @param {string} methodName - Name of a single method to be defined. 450 * @param {Function} methodFn - Function body of a single method to be defined. 451 ***/ 452 setProperty(sugarNamespace, 'defineStaticPolyfill', function(arg1, arg2, arg3) { 453 var opts = collectDefineOptions(arg1, arg2, arg3); 454 extendNative(globalContext[name], opts.methods, true, opts.last); 455 return sugarNamespace; 456 }); 457 458 /*** 459 * @method defineInstancePolyfill(methods) 460 * @returns SugarNamespace 461 * @namespace SugarNamespace 462 * @short Defines instance methods that are mapped onto the native prototype 463 * if they do not already exist. 464 * @extra Intended only for use creating polyfills that follow the ECMAScript 465 * spec. Accepts either a single object mapping names to functions, or 466 * name and function as two arguments. This method differs from 467 * `defineInstance` as there is no static signature (as the method 468 * is mapped as-is to the native), so it should refer to its `this` 469 * object. 470 * 471 * @example 472 * 473 * Sugar.Array.defineInstancePolyfill({ 474 * indexOf: function (arr, el) { 475 * // index finding code here! 476 * } 477 * }); 478 * 479 * @signature defineInstancePolyfill(methodName, methodFn) 480 * @param {Object} methods - Methods to be defined. 481 * @param {string} methodName - Name of a single method to be defined. 482 * @param {Function} methodFn - Function body of a single method to be defined. 483 ***/ 484 setProperty(sugarNamespace, 'defineInstancePolyfill', function(arg1, arg2, arg3) { 485 var opts = collectDefineOptions(arg1, arg2, arg3); 486 extendNative(globalContext[name].prototype, opts.methods, true, opts.last); 487 // Map instance polyfills to chainable as well.
488 forEachProperty(opts.methods, function(fn, methodName) { 489 defineChainableMethod(sugarNamespace, methodName, fn); 490 }); 491 return sugarNamespace; 492 }); 493 494 /*** 495 * @method alias(toName, from) 496 * @returns SugarNamespace 497 * @namespace SugarNamespace 498 * @short Aliases one Sugar method to another. 499 * 500 * @example 501 * 502 * Sugar.Array.alias('all', 'every'); 503 * 504 * @signature alias(toName, fn) 505 * @param {string} toName - Name for new method. 506 * @param {string|Function} from - Method to alias, or string shortcut. 507 ***/ 508 setProperty(sugarNamespace, 'alias', function(name, source) { 509 var method = typeof source === 'string' ? sugarNamespace[source] : source; 510 setMethod(sugarNamespace, name, method); 511 return sugarNamespace; 512 }); 513 514 // Each namespace can extend only itself through its .extend method. 515 setProperty(sugarNamespace, 'extend', extend); 516 517 // Cache the class to namespace relationship for later use. 518 namespacesByName[name] = sugarNamespace; 519 namespacesByClassString['[object ' + name + ']'] = sugarNamespace; 520 521 mapNativeToChainable(name); 522 mapObjectChainablesToNamespace(sugarNamespace); 523 524 525 // Export 526 return Sugar[name] = sugarNamespace; 527 } 528 529 function setGlobalProperties() { 530 setProperty(Sugar, 'extend', Sugar); 531 setProperty(Sugar, 'toString', toString); 532 setProperty(Sugar, 'createNamespace', createNamespace); 533 534 setProperty(Sugar, 'util', { 535 'hasOwn': hasOwn, 536 'getOwn': getOwn, 537 'setProperty': setProperty, 538 'classToString': classToString, 539 'defineProperty': defineProperty, 540 'forEachProperty': forEachProperty, 541 'mapNativeToChainable': mapNativeToChainable 542 }); 543 } 544 545 function toString() { 546 return SUGAR_GLOBAL; 547 } 548 549 550 // Defining Methods 551 552 function defineMethods(sugarNamespace, methods, type, args, flags) { 553 forEachProperty(methods, function(method, methodName) { 554 var instanceMethod, staticMethod = method; 555 if (args) { 556 staticMethod = wrapMethodWithArguments(method); 557 } 558 if (flags) { 559 staticMethod.flags = flags; 560 } 561 562 // A method may define its own custom implementation, so 563 // make sure that's not the case before creating one. 564 if (type & INSTANCE && !method.instance) { 565 instanceMethod = wrapInstanceMethod(method, args); 566 setProperty(staticMethod, 'instance', instanceMethod); 567 } 568 569 if (type & STATIC) { 570 setProperty(staticMethod, 'static', true); 571 } 572 573 setMethod(sugarNamespace, methodName, staticMethod); 574 575 if (sugarNamespace.active) { 576 // If the namespace has been activated (.extend has been called), 577 // then map this method as well. 578 sugarNamespace.extend(methodName); 579 } 580 }); 581 } 582 583 function collectDefineOptions(arg1, arg2, arg3) { 584 var methods, last; 585 if (typeof arg1 === 'string') { 586 methods = {}; 587 methods[arg1] = arg2; 588 last = arg3; 589 } else { 590 methods = arg1; 591 last = arg2; 592 } 593 return { 594 last: last, 595 methods: methods 596 }; 597 } 598 599 function wrapInstanceMethod(fn, args) { 600 return args ? wrapMethodWithArguments(fn, true) : wrapInstanceMethodFixed(fn); 601 } 602 603 function wrapMethodWithArguments(fn, instance) { 604 // Functions accepting enumerated arguments will always have "args" as the 605 // last argument, so subtract one from the function length to get the point 606 // at which to start collecting arguments. If this is an instance method on 607 // a prototype, then "this" will be pushed into the arguments array so start 608 // collecting 1 argument earlier. 609 var startCollect = fn.length - 1 - (instance ? 1 : 0); 610 return function() { 611 var args = [], collectedArgs = [], len; 612 if (instance) { 613 args.push(this); 614 } 615 len = Math.max(arguments.length, startCollect); 616 // Optimized: no leaking arguments 617 for (var i = 0; i < len; i++) { 618 if (i < startCollect) { 619 args.push(arguments[i]); 620 } else { 621 collectedArgs.push(arguments[i]); 622 } 623 } 624 args.push(collectedArgs); 625 return fn.apply(this, args); 626 }; 627 } 628 629 function wrapInstanceMethodFixed(fn) { 630 switch (fn.length) { 631 // Wrapped instance methods will always be passed the instance 632 // as the first argument, but requiring the argument to be defined 633 // may cause confusion here, so return the same wrapped function regardless. 634 case 0: 635 case 1: 636 return function() { 637 return fn(this); 638 }; 639 case 2: 640 return function(a) { 641 return fn(this, a); 642 }; 643 case 3: 644 return function(a, b) { 645 return fn(this, a, b); 646 }; 647 case 4: 648 return function(a, b, c) { 649 return fn(this, a, b, c); 650 }; 651 case 5: 652 return function(a, b, c, d) { 653 return fn(this, a, b, c, d); 654 }; 655 } 656 } 657 658 // Method helpers 659 660 function extendNative(target, source, polyfill, override) {
661 forEachProperty(source, function(method, name) { 662 if (polyfill && !override && target[name]) { 663 // Method exists, so bail. 664 return; 665 } 666 setProperty(target, name, method); 667 }); 668 } 669 670 function setMethod(sugarNamespace, methodName, method) { 671 sugarNamespace[methodName] = method; 672 if (method.instance) { 673 defineChainableMethod(sugarNamespace, methodName, method.instance, true); 674 } 675 } 676 677 678 // Chainables 679 680 function getNewChainableClass(name) { 681 var fn = function SugarChainable(obj, arg) { 682 if (!(this instanceof fn)) { 683 return new fn(obj, arg); 684 } 685 if (this.constructor !== fn) { 686 // Allow modules to define their own constructors. 687 obj = this.constructor.apply(obj, arguments); 688 } 689 this.raw = obj; 690 }; 691 setProperty(fn, 'toString', function() { 692 return SUGAR_GLOBAL + name; 693 }); 694 setProperty(fn.prototype, 'valueOf', function() { 695 return this.raw; 696 }); 697 return fn; 698 } 699 700 function defineChainableMethod(sugarNamespace, methodName, fn) { 701 var wrapped = wrapWithChainableResult(fn), existing, collision, dcp; 702 dcp = DefaultChainable.prototype; 703 existing = dcp[methodName]; 704 705 // If the method was previously defined on the default chainable, then a 706 // collision exists, so set the method to a disambiguation function that will 707 // lazily evaluate the object and find it's associated chainable. An extra 708 // check is required to avoid false positives from Object inherited methods. 709 collision = existing && existing !== Object.prototype[methodName]; 710 711 // The disambiguation function is only required once. 712 if (!existing || !existing.disambiguate) { 713 dcp[methodName] = collision ? disambiguateMethod(methodName) : wrapped; 714 } 715 716 // The target chainable always receives the wrapped method. Additionally, 717 // if the target chainable is Sugar.Object, then map the wrapped method 718 // to all other namespaces as well if they do not define their own method 719 // of the same name. This way, a Sugar.Number will have methods like 720 // isEqual that can be called on any object without having to traverse up 721 // the prototype chain and perform disambiguation, which costs cycles. 722 // Note that the "if" block below actually does nothing on init as Object 723 // goes first and no other namespaces exist yet. However it needs to be 724 // here as Object instance methods defined later also need to be mapped 725 // back onto existing namespaces. 726 sugarNamespace.prototype[methodName] = wrapped; 727 if (sugarNamespace === Sugar.Object) { 728 mapObjectChainableToAllNamespaces(methodName, wrapped); 729 } 730 } 731 732 function mapObjectChainablesToNamespace(sugarNamespace) { 733 forEachProperty(Sugar.Object && Sugar.Object.prototype, function(val, methodName) { 734 if (typeof val === 'function') { 735 setObjectChainableOnNamespace(sugarNamespace, methodName, val); 736 } 737 }); 738 } 739 740 function mapObjectChainableToAllNamespaces(methodName, fn) { 741 forEachProperty(namespacesByName, function(sugarNamespace) { 742 setObjectChainableOnNamespace(sugarNamespace, methodName, fn); 743 }); 744 } 745 746 function setObjectChainableOnNamespace(sugarNamespace, methodName, fn) { 747 var proto = sugarNamespace.prototype; 748 if (!hasOwn(proto, methodName)) { 749 proto[methodName] = fn; 750 } 751 } 752 753 function wrapWithChainableResult(fn) { 754 return function() { 755 return new DefaultChainable(fn.apply(this.raw, arguments)); 756 }; 757 } 758 759 function disambiguateMethod(methodName) { 760 var fn = function() { 761 var raw = this.raw, sugarNamespace, fn; 762 if (raw != null) { 763 // Find the Sugar namespace for this unknown. 764 sugarNamespace = namespacesByClassString[classToString(raw)]; 765 } 766 if (!sugarNamespace) { 767 // If no sugarNamespace can be resolved, then default 768 // back to Sugar.Object so that undefined and other 769 // non-supported types can still have basic object 770 // methods called on them, such as type checks. 771 sugarNamespace = Sugar.Object; 772 } 773 774 fn = new sugarNamespace(raw)[methodName]; 775 776 if (fn.disambiguate) { 777 // If the method about to be called on this chainable is 778 // itself a disambiguation method, then throw an error to 779 // prevent infinite recursion. 780 throw new TypeError('Cannot resolve namespace for ' + raw); 781 } 782 783 return fn.apply(this, arguments); 784 }; 785 fn.disambiguate = true; 786 return fn; 787 } 788 789 function mapNativeToChainable(name, methodNames) { 790 var sugarNamespace = namespacesByName[name], 791 nativeProto = globalContext[name].prototype; 792 793 if (!methodNames && ownPropertyNames) { 794 methodNames = ownPropertyNames(nativeProto); 795 } 796
797 forEachProperty(methodNames, function(methodName) { 798 if (nativeMethodProhibited(methodName)) { 799 // Sugar chainables have their own constructors as well as "valueOf" 800 // methods, so exclude them here. The __proto__ argument should be trapped 801 // by the function check below, however simply accessing this property on 802 // Object.prototype causes QML to segfault, so pre-emptively excluding it. 803 return; 804 } 805 try { 806 var fn = nativeProto[methodName]; 807 if (typeof fn !== 'function') { 808 // Bail on anything not a function. 809 return; 810 } 811 } catch (e) { 812 // Function.prototype has properties that 813 // will throw errors when accessed. 814 return; 815 } 816 defineChainableMethod(sugarNamespace, methodName, fn); 817 }); 818 } 819 820 function nativeMethodProhibited(methodName) { 821 return methodName === 'constructor' || 822 methodName === 'valueOf' || 823 methodName === '__proto__'; 824 } 825 826 827 // Util 828 829 // Internal references 830 var ownPropertyNames = Object.getOwnPropertyNames, 831 internalToString = Object.prototype.toString, 832 internalHasOwnProperty = Object.prototype.hasOwnProperty; 833 834 // Defining this as a variable here as the ES5 module 835 // overwrites it to patch DONTENUM. 836 var forEachProperty = function(obj, fn) { 837 for (var key in obj) { 838 if (!hasOwn(obj, key)) continue; 839 if (fn.call(obj, obj[key], key, obj) === false) break; 840 } 841 }; 842 843 function definePropertyShim(obj, prop, descriptor) { 844 obj[prop] = descriptor.value; 845 } 846 847 function setProperty(target, name, value, enumerable) { 848 defineProperty(target, name, { 849 value: value, 850 enumerable: !!enumerable, 851 configurable: true, 852 writable: true 853 }); 854 } 855 856 // PERF: Attempts to speed this method up get very Heisenbergy. Quickly 857 // returning based on typeof works for primitives, but slows down object 858 // types. Even === checks on null and undefined (no typeof) will end up 859 // basically breaking even. This seems to be as fast as it can go. 860 function classToString(obj) { 861 return internalToString.call(obj); 862 } 863 864 function hasOwn(obj, prop) { 865 return !!obj && internalHasOwnProperty.call(obj, prop); 866 } 867 868 function getOwn(obj, prop) { 869 if (hasOwn(obj, prop)) { 870 return obj[prop]; 871 } 872 } 873 874 setupGlobal(); 875 876 /*** 877 * @module Common 878 * @description Internal utility and common methods. 879 ***/ 880 881 // Flag allowing native methods to be enhanced 882 var ENHANCEMENTS_FLAG = 'enhance'; 883 884 // For type checking, etc. Excludes object as this is more nuanced. 885 var NATIVE_TYPES = 'Boolean Number String Date RegExp Function Array Error Set Map'; 886 887 // Do strings have no keys? 888 var NO_KEYS_IN_STRING_OBJECTS = !('0' in Object('a')); 889 890 // Prefix for private properties 891 var PRIVATE_PROP_PREFIX = '_sugar_'; 892 893 // Matches 1..2 style ranges in properties 894 var PROPERTY_RANGE_REG = /^(.*?)\[([-\d]*)\.\.([-\d]*)\](.*)$/; 895 896 // WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in the Space, Separator category. 897 var TRIM_CHARS = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF'; 898 899 // Regex for matching a formatted string 900 var STRING_FORMAT_REG = /([{}])\1|\{([^}]*)\}|(%)%|(%(\w*))/g; 901 902 // Common chars 903 var HALF_WIDTH_ZERO = 0x30, 904 FULL_WIDTH_ZERO = 0xff10, 905 HALF_WIDTH_PERIOD = '.', 906 FULL_WIDTH_PERIOD = 'ï¼', 907 HALF_WIDTH_COMMA = ',', 908 OPEN_BRACE = '{', 909 CLOSE_BRACE = '}'; 910 911 // Namespace aliases 912 var sugarObject = Sugar.Object, 913 sugarArray = Sugar.Array, 914 sugarDate = Sugar.Date, 915 sugarString = Sugar.String, 916 sugarNumber = Sugar.Number, 917 sugarFunction = Sugar.Function, 918 sugarRegExp = Sugar.RegExp; 919 920 // Class checks 921 var isSerializable, 922 isBoolean, isNumber, isString, 923 isDate, isRegExp, isFunction, 924 isArray, isSet, isMap, isError; 925 926 function buildClassChecks() { 927 928 var knownTypes = {}; 929 930 function addCoreTypes() { 931 932 var names = spaceSplit(NATIVE_TYPES); 933 934 isBoolean = buildPrimitiveClassCheck(names[0]); 935 isNumber = buildPrimitiveClassCheck(names[1]); 936 isString = buildPrimitiveClassCheck(names[2]); 937 938 isDate = buildClassCheck(names[3]); 939 isRegExp = buildClassCheck(names[4]); 940 941 // Wanted to enhance performance here by using simply "typeof" 942 // but Firefox has two major issues that make this impossible, 943 // one fixed, the other not, so perform a full class check here. 944 // 945 // 1. Regexes can be typeof "function" in FF < 3 946 // https://bugzilla.mozilla.org/show_bug.cgi?id=61911 (fixed) 947 // 948 // 2. HTMLEmbedElement and HTMLObjectElement are be typeof "function" 949 // https://bugzilla.mozilla.org/show_bug.cgi?id=268945 (won't fix) 950 isFunction = buildClassCheck(names[5]); 951 952 953 isArray = Array.isArray || buildClassCheck(names[6]); 954 isError = buildClassCheck(names[7]); 955 956 isSet = buildClassCheck(names[8], typeof Set !== 'undefined' && Set); 957 isMap = buildClassCheck(names[9], typeof Map !== 'undefined' && Map); 958 959 // Add core types as known so that they can be checked by value below, 960 // notably excluding Functions and adding Arguments and Error. 961 addKnownType('Arguments'); 962 addKnownType(names[0]); 963 addKnownType(names[1]); 964 addKnownType(names[2]); 965 addKnownType(names[3]); 966 addKnownType(names[4]); 967 addKnownType(names[6]); 968 969 } 970 971 function addArrayTypes() { 972 var types = 'Int8 Uint8 Uint8Clamped Int16 Uint16 Int32 Uint32 Float32 Float64';
973 forEach(spaceSplit(types), function(str) { 974 addKnownType(str + 'Array'); 975 }); 976 } 977 978 function addKnownType(className) { 979 var str = '[object ' + className + ']'; 980 knownTypes[str] = true; 981 } 982 983 function isKnownType(className) { 984 return knownTypes[className]; 985 } 986 987 function buildClassCheck(className, globalObject) { 988 if (globalObject && isClass(new globalObject, 'Object')) { 989 return getConstructorClassCheck(globalObject); 990 } else { 991 return getToStringClassCheck(className); 992 } 993 } 994 995 function getConstructorClassCheck(obj) { 996 var ctorStr = String(obj); 997 return function(obj) { 998 return String(obj.constructor) === ctorStr; 999 }; 1000 } 1001 1002 function getToStringClassCheck(className) { 1003 return function(obj, str) { 1004 // perf: Returning up front on instanceof appears to be slower. 1005 return isClass(obj, className, str); 1006 }; 1007 } 1008 1009 function buildPrimitiveClassCheck(className) { 1010 var type = className.toLowerCase(); 1011 return function(obj) { 1012 var t = typeof obj; 1013 return t === type || t === 'object' && isClass(obj, className); 1014 }; 1015 } 1016 1017 addCoreTypes(); 1018 addArrayTypes(); 1019 1020 isSerializable = function(obj, className) { 1021 // Only known objects can be serialized. This notably excludes functions, 1022 // host objects, Symbols (which are matched by reference), and instances 1023 // of classes. The latter can arguably be matched by value, but 1024 // distinguishing between these and host objects -- which should never be 1025 // compared by value -- is very tricky so not dealing with it here. 1026 className = className || classToString(obj); 1027 return isKnownType(className) || isPlainObject(obj, className); 1028 }; 1029 1030 } 1031 1032 function isClass(obj, className, str) { 1033 if (!str) { 1034 str = classToString(obj); 1035 } 1036 return str === '[object ' + className + ']'; 1037 } 1038 1039 // Wrapping the core's "define" methods to 1040 // save a few bytes in the minified script. 1041 function wrapNamespace(method) { 1042 return function(sugarNamespace, arg1, arg2) { 1043 sugarNamespace[method](arg1, arg2); 1044 }; 1045 } 1046 1047 // Method define aliases 1048 var alias = wrapNamespace('alias'), 1049 defineStatic = wrapNamespace('defineStatic'), 1050 defineInstance = wrapNamespace('defineInstance'), 1051 defineStaticPolyfill = wrapNamespace('defineStaticPolyfill'), 1052 defineInstancePolyfill = wrapNamespace('defineInstancePolyfill'), 1053 defineInstanceAndStatic = wrapNamespace('defineInstanceAndStatic'), 1054 defineInstanceWithArguments = wrapNamespace('defineInstanceWithArguments'); 1055 1056 function defineInstanceSimilar(sugarNamespace, set, fn, flags) { 1057 defineInstance(sugarNamespace, collectSimilarMethods(set, fn), flags); 1058 } 1059 1060 function defineInstanceAndStaticSimilar(sugarNamespace, set, fn, flags) { 1061 defineInstanceAndStatic(sugarNamespace, collectSimilarMethods(set, fn), flags); 1062 } 1063 1064 function collectSimilarMethods(set, fn) { 1065 var methods = {}; 1066 if (isString(set)) { 1067 set = spaceSplit(set); 1068 } 1069 forEach(set, function(el, i) { 1070 fn(methods, el, i); 1071 }); 1072 return methods; 1073 } 1074 1075 // This song and dance is to fix methods to a different length 1076 // from what they actually accept in order to stay in line with 1077 // spec. Additionally passing argument length, as some methods 1078 // throw assertion errors based on this (undefined check is not 1079 // enough). Fortunately for now spec is such that passing 3 1080 // actual arguments covers all requirements. Note that passing 1081 // the argument length also forces the compiler to not rewrite 1082 // length of the compiled function. 1083 function fixArgumentLength(fn) { 1084 var staticFn = function(a) { 1085 var args = arguments; 1086 return fn(a, args[1], args[2], args.length - 1); 1087 }; 1088 staticFn.instance = function(b) { 1089 var args = arguments; 1090 return fn(this, b, args[1], args.length); 1091 }; 1092 return staticFn; 1093 } 1094 1095 function defineAccessor(namespace, name, fn) { 1096 setProperty(namespace, name, fn); 1097 } 1098 1099 function defineOptionsAccessor(namespace, defaults) { 1100 var obj = simpleClone(defaults); 1101 1102 function getOption(name) { 1103 return obj[name]; 1104 } 1105 1106 function setOption(arg1, arg2) { 1107 var options; 1108 if (arguments.length === 1) { 1109 options = arg1; 1110 } else { 1111 options = {}; 1112 options[arg1] = arg2; 1113 }
1114 forEachProperty(options, function(val, name) { 1115 if (val === null) { 1116 val = defaults[name]; 1117 } 1118 obj[name] = val; 1119 }); 1120 } 1121 1122 defineAccessor(namespace, 'getOption', getOption); 1123 defineAccessor(namespace, 'setOption', setOption); 1124 return getOption; 1125 } 1126 1127 // For methods defined directly on the prototype like Range 1128 function defineOnPrototype(ctor, methods) { 1129 var proto = ctor.prototype; 1130 forEachProperty(methods, function(val, key) { 1131 proto[key] = val; 1132 }); 1133 } 1134 1135 // Argument helpers 1136 1137 function assertArgument(exists) { 1138 if (!exists) { 1139 throw new TypeError('Argument required'); 1140 } 1141 } 1142 1143 function assertCallable(obj) { 1144 if (!isFunction(obj)) { 1145 throw new TypeError('Function is not callable'); 1146 } 1147 } 1148 1149 function assertArray(obj) { 1150 if (!isArray(obj)) { 1151 throw new TypeError('Array required'); 1152 } 1153 } 1154
1155 function assertWritable(obj) { 1156 if (isPrimitive(obj)) { 1157 // If strict mode is active then primitives will throw an 1158 // error when attempting to write properties. We can't be 1159 // sure if strict mode is available, so pre-emptively 1160 // throw an error here to ensure consistent behavior. 1161 throw new TypeError('Property cannot be written'); 1162 } 1163 } 1164 1165 // Coerces an object to a positive integer. 1166 // Does not allow Infinity. 1167 function coercePositiveInteger(n) { 1168 n = +n || 0; 1169 if (n < 0 || !isNumber(n) || !isFinite(n)) { 1170 throw new RangeError('Invalid number'); 1171 } 1172 return trunc(n); 1173 } 1174 1175 1176 // General helpers 1177 1178 function isDefined(o) { 1179 return o !== undefined; 1180 } 1181 1182 function isUndefined(o) { 1183 return o === undefined; 1184 } 1185 1186 function privatePropertyAccessor(key) { 1187 var privateKey = PRIVATE_PROP_PREFIX + key; 1188 return function(obj, val) { 1189 if (arguments.length > 1) { 1190 setProperty(obj, privateKey, val); 1191 return obj; 1192 } 1193 return obj[privateKey]; 1194 }; 1195 } 1196 1197 function setChainableConstructor(sugarNamespace, createFn) { 1198 sugarNamespace.prototype.constructor = function() { 1199 return createFn.apply(this, arguments); 1200 }; 1201 } 1202 1203 // Fuzzy matching helpers 1204 1205 function getMatcher(f) { 1206 if (!isPrimitive(f)) { 1207 var className = classToString(f); 1208 if (isRegExp(f, className)) { 1209 return regexMatcher(f); 1210 } else if (isDate(f, className)) { 1211 return dateMatcher(f); 1212 } else if (isFunction(f, className)) { 1213 return functionMatcher(f); 1214 } else if (isPlainObject(f, className)) { 1215 return fuzzyMatcher(f); 1216 } 1217 } 1218 // Default is standard isEqual 1219 return defaultMatcher(f); 1220 } 1221 1222 function fuzzyMatcher(obj) { 1223 var matchers = {}; 1224 return function(el, i, arr) { 1225 var matched = true; 1226 if (!isObjectType(el)) { 1227 return false; 1228 } 1229 forEachProperty(obj, function(val, key) { 1230 matchers[key] = getOwn(matchers, key) || getMatcher(val); 1231 if (matchers[key].call(arr, el[key], i, arr) === false) { 1232 matched = false; 1233 } 1234 return matched; 1235 }); 1236 return matched; 1237 }; 1238 } 1239 1240 function defaultMatcher(f) { 1241 return function(el) { 1242 return isEqual(el, f); 1243 }; 1244 } 1245 1246 function regexMatcher(reg) { 1247 reg = RegExp(reg); 1248 return function(el) { 1249 return reg.test(el); 1250 }; 1251 } 1252 1253 function dateMatcher(d) { 1254 var ms = d.getTime(); 1255 return function(el) { 1256 return !!(el && el.getTime) && el.getTime() === ms; 1257 }; 1258 } 1259 1260 function functionMatcher(fn) { 1261 return function(el, i, arr) { 1262 // Return true up front if match by reference 1263 return el === fn || fn.call(arr, el, i, arr); 1264 }; 1265 } 1266 1267 // Object helpers 1268 1269 function getKeys(obj) { 1270 return Object.keys(obj); 1271 } 1272 1273 function deepHasProperty(obj, key, any) { 1274 return handleDeepProperty(obj, key, any, true); 1275 } 1276 1277 function deepGetProperty(obj, key, any) { 1278 return handleDeepProperty(obj, key, any, false); 1279 } 1280 1281 function deepSetProperty(obj, key, val) { 1282 handleDeepProperty(obj, key, false, false, true, false, val); 1283 return obj; 1284 } 1285 1286 function handleDeepProperty(obj, key, any, has, fill, fillLast, val) { 1287 var ns, bs, ps, cbi, set, isLast, isPush, isIndex, nextIsIndex, exists; 1288 ns = obj || undefined; 1289 if (key == null) return; 1290 1291 if (isObjectType(key)) { 1292 // Allow array and array-like accessors 1293 bs = [key]; 1294 } else { 1295 key = String(key); 1296 if (key.indexOf('..') !== -1) { 1297 return handleArrayIndexRange(obj, key, any, val); 1298 } 1299 bs = key.split('['); 1300 } 1301 1302 set = isDefined(val); 1303 1304 for (var i = 0, blen = bs.length; i < blen; i++) { 1305 ps = bs[i]; 1306 1307 if (isString(ps)) { 1308 ps = periodSplit(ps); 1309 } 1310 1311 for (var j = 0, plen = ps.length; j < plen; j++) { 1312 key = ps[j]; 1313 1314 // Is this the last key? 1315 isLast = i === blen - 1 && j === plen - 1; 1316 1317 // Index of the closing ] 1318 cbi = key.indexOf(']'); 1319 1320 // Is the key an array index? 1321 isIndex = cbi !== -1; 1322 1323 // Is this array push syntax "[]"? 1324 isPush = set && cbi === 0; 1325 1326 // If the bracket split was successful and this is the last element 1327 // in the dot split, then we know the next key will be an array index. 1328 nextIsIndex = blen > 1 && j === plen - 1; 1329 1330 if (isPush) { 1331 // Set the index to the end of the array 1332 key = ns.length; 1333 } else if (isIndex) { 1334 // Remove the closing ] 1335 key = key.slice(0, -1); 1336 } 1337 1338 // If the array index is less than 0, then 1339 // add its length to allow negative indexes. 1340 if (isIndex && key < 0) { 1341 key = +key + ns.length; 1342 } 1343 1344 // Bracket keys may look like users[5] or just [5], so the leading 1345 // characters are optional. We can enter the namespace if this is the 1346 // 2nd part, if there is only 1 part, or if there is an explicit key. 1347 if (i || key || blen === 1) { 1348 1349 exists = any ? key in ns : hasOwn(ns, key); 1350 1351 // Non-existent namespaces are only filled if they are intermediate 1352 // (not at the end) or explicitly filling the last. 1353 if (fill && (!isLast || fillLast) && !exists) { 1354 // For our purposes, last only needs to be an array. 1355 ns = ns[key] = nextIsIndex || (fillLast && isLast) ? [] : {}; 1356 continue; 1357 } 1358 1359 if (has) { 1360 if (isLast || !exists) { 1361 return exists; 1362 } 1363 } else if (set && isLast) { 1364 assertWritable(ns); 1365 ns[key] = val; 1366 } 1367 1368 ns = exists ? ns[key] : undefined; 1369 } 1370 1371 } 1372 } 1373 return ns; 1374 } 1375 1376 // Get object property with support for 0..1 style range notation. 1377 function handleArrayIndexRange(obj, key, any, val) { 1378 var match, start, end, leading, trailing, arr, set; 1379 match = key.match(PROPERTY_RANGE_REG); 1380 if (!match) { 1381 return; 1382 } 1383 1384 set = isDefined(val); 1385 leading = match[1]; 1386 1387 if (leading) { 1388 arr = handleDeepProperty(obj, leading, any, false, set ? true : false, true); 1389 } else { 1390 arr = obj; 1391 } 1392 1393 assertArray(arr); 1394 1395 trailing = match[4]; 1396 start = match[2] ? +match[2] : 0; 1397 end = match[3] ? +match[3] : arr.length; 1398 1399 // A range of 0..1 is inclusive, so we need to add 1 to the end. If this 1400 // pushes the index from -1 to 0, then set it to the full length of the 1401 // array, otherwise it will return nothing. 1402 end = end === -1 ? arr.length : end + 1; 1403 1404 if (set) { 1405 for (var i = start; i < end; i++) { 1406 handleDeepProperty(arr, i + trailing, any, false, true, false, val); 1407 } 1408 } else { 1409 arr = arr.slice(start, end); 1410 1411 // If there are trailing properties, then they need to be mapped for each 1412 // element in the array. 1413 if (trailing) {
1414 if (trailing.charAt(0) === HALF_WIDTH_PERIOD) { 1415 // Need to chomp the period if one is trailing after the range. We 1416 // can't do this at the regex level because it will be required if 1417 // we're setting the value as it needs to be concatentated together 1418 // with the array index to be set. 1419 trailing = trailing.slice(1); 1420 } 1421 return arr.map(function(el) { 1422 return handleDeepProperty(el, trailing); 1423 }); 1424 } 1425 } 1426 return arr; 1427 } 1428 1429 function getOwnKey(obj, key) { 1430 if (hasOwn(obj, key)) { 1431 return key; 1432 } 1433 } 1434 1435 function hasProperty(obj, prop) { 1436 return !isPrimitive(obj) && prop in obj; 1437 } 1438 1439 function isObjectType(obj, type) { 1440 return !!obj && (type || typeof obj) === 'object'; 1441 } 1442 1443 function isPrimitive(obj, type) { 1444 type = type || typeof obj; 1445 return obj == null || type === 'string' || type === 'number' || type === 'boolean'; 1446 } 1447 1448 function isPlainObject(obj, className) { 1449 return isObjectType(obj) && 1450 isClass(obj, 'Object', className) && 1451 hasValidPlainObjectPrototype(obj) && 1452 hasOwnEnumeratedProperties(obj); 1453 } 1454 1455 function hasValidPlainObjectPrototype(obj) { 1456 var hasToString = 'toString' in obj; 1457 var hasConstructor = 'constructor' in obj; 1458 // An object created with Object.create(null) has no methods in the 1459 // prototype chain, so check if any are missing. The additional hasToString 1460 // check is for false positives on some host objects in old IE which have 1461 // toString but no constructor. If the object has an inherited constructor, 1462 // then check if it is Object (the "isPrototypeOf" tapdance here is a more 1463 // robust way of ensuring this if the global has been hijacked). Note that 1464 // accessing the constructor directly (without "in" or "hasOwnProperty") 1465 // will throw a permissions error in IE8 on cross-domain windows. 1466 return (!hasConstructor && !hasToString) || 1467 (hasConstructor && !hasOwn(obj, 'constructor') && 1468 hasOwn(obj.constructor.prototype, 'isPrototypeOf')); 1469 } 1470 1471 function hasOwnEnumeratedProperties(obj) { 1472 // Plain objects are generally defined as having enumerated properties 1473 // all their own, however in early IE environments without defineProperty, 1474 // there may also be enumerated methods in the prototype chain, so check 1475 // for both of these cases. 1476 var objectProto = Object.prototype; 1477 for (var key in obj) { 1478 var val = obj[key]; 1479 if (!hasOwn(obj, key) && val !== objectProto[key]) { 1480 return false; 1481 } 1482 } 1483 return true; 1484 } 1485 1486 function simpleRepeat(n, fn) { 1487 for (var i = 0; i < n; i++) { 1488 fn(i); 1489 } 1490 } 1491 1492 function simpleClone(obj) { 1493 return simpleMerge({}, obj); 1494 } 1495 1496 function simpleMerge(target, source) { 1497 forEachProperty(source, function(val, key) { 1498 target[key] = val; 1499 }); 1500 return target; 1501 } 1502 1503 // Make primtives types like strings into objects. 1504 function coercePrimitiveToObject(obj) { 1505 if (isPrimitive(obj)) { 1506 obj = Object(obj); 1507 } 1508 if (NO_KEYS_IN_STRING_OBJECTS && isString(obj)) { 1509 forceStringCoercion(obj); 1510 } 1511 return obj; 1512 } 1513 1514 // Force strings to have their indexes set in 1515 // environments that don't do this automatically. 1516 function forceStringCoercion(obj) { 1517 var i = 0, chr; 1518 while (chr = obj.charAt(i)) { 1519 obj[i++] = chr; 1520 } 1521 } 1522 1523 // Equality helpers 1524 1525 function isEqual(a, b, stack) { 1526 var aClass, bClass; 1527 if (a === b) { 1528 // Return quickly up front when matched by reference, 1529 // but be careful about 0 !== -0. 1530 return a !== 0 || 1 / a === 1 / b; 1531 } 1532 aClass = classToString(a); 1533 bClass = classToString(b); 1534 if (aClass !== bClass) { 1535 return false; 1536 } 1537 1538 if (isSerializable(a, aClass) && isSerializable(b, bClass)) { 1539 return objectIsEqual(a, b, aClass, stack); 1540 } else if (isSet(a, aClass) && isSet(b, bClass)) { 1541 return a.size === b.size && isEqual(setToArray(a), setToArray(b), stack); 1542 } else if (isMap(a, aClass) && isMap(b, bClass)) { 1543 return a.size === b.size && isEqual(mapToArray(a), mapToArray(b), stack); 1544 } else if (isError(a, aClass) && isError(b, bClass)) { 1545 return a.toString() === b.toString(); 1546 } 1547 1548 return false; 1549 } 1550 1551 function objectIsEqual(a, b, aClass, stack) { 1552 var aType = typeof a, bType = typeof b, propsEqual, count; 1553 if (aType !== bType) { 1554 return false; 1555 } 1556 if (isObjectType(a.valueOf())) { 1557 if (a.length !== b.length) { 1558 // perf: Quickly returning up front for arrays. 1559 return false; 1560 } 1561 count = 0; 1562 propsEqual = true;
1563 iterateWithCyclicCheck(a, false, stack, function(key, val, cyc, stack) { 1564 if (!cyc && (!(key in b) || !isEqual(val, b[key], stack))) { 1565 propsEqual = false; 1566 } 1567 count++; 1568 return propsEqual; 1569 }); 1570 if (!propsEqual || count !== getKeys(b).length) { 1571 return false; 1572 } 1573 } 1574 // Stringifying the value handles NaN, wrapped primitives, dates, and errors in one go. 1575 return a.valueOf().toString() === b.valueOf().toString(); 1576 } 1577 1578 // Serializes an object in a way that will provide a token unique 1579 // to the type, class, and value of an object. Host objects, class 1580 // instances etc, are not serializable, and are held in an array 1581 // of references that will return the index as a unique identifier 1582 // for the object. This array is passed from outside so that the 1583 // calling function can decide when to dispose of this array. 1584 function serializeInternal(obj, refs, stack) { 1585 var type = typeof obj, className, value, ref; 1586 1587 // Return quickly for primitives to save cycles 1588 if (isPrimitive(obj, type) && !isRealNaN(obj)) { 1589 return type + obj; 1590 } 1591 1592 className = classToString(obj); 1593 1594 if (!isSerializable(obj, className)) { 1595 ref = indexOf(refs, obj); 1596 if (ref === -1) { 1597 ref = refs.length; 1598 refs.push(obj); 1599 } 1600 return ref; 1601 } else if (isObjectType(obj)) { 1602 value = serializeDeep(obj, refs, stack) + obj.toString(); 1603 } else if (1 / obj === -Infinity) { 1604 value = '-0'; 1605 } else if (obj.valueOf) { 1606 value = obj.valueOf(); 1607 } 1608 return type + className + value; 1609 } 1610 1611 function serializeDeep(obj, refs, stack) { 1612 var result = ''; 1613 iterateWithCyclicCheck(obj, true, stack, function(key, val, cyc, stack) { 1614 result += cyc ? 'CYC' : key + serializeInternal(val, refs, stack); 1615 }); 1616 return result; 1617 } 1618 1619 function iterateWithCyclicCheck(obj, sortedKeys, stack, fn) { 1620 1621 function next(val, key) { 1622 var cyc = false; 1623 1624 // Allowing a step into the structure before triggering this check to save 1625 // cycles on standard JSON structures and also to try as hard as possible to 1626 // catch basic properties that may have been modified. 1627 if (stack.length > 1) { 1628 var i = stack.length; 1629 while (i--) { 1630 if (stack[i] === val) { 1631 cyc = true; 1632 } 1633 } 1634 } 1635 1636 stack.push(val); 1637 fn(key, val, cyc, stack); 1638 stack.pop(); 1639 } 1640 1641 function iterateWithSortedKeys() { 1642 // Sorted keys is required for serialization, where object order 1643 // does not matter but stringified order does. 1644 var arr = getKeys(obj).sort(), key; 1645 for (var i = 0; i < arr.length; i++) { 1646 key = arr[i]; 1647 next(obj[key], arr[i]); 1648 } 1649 } 1650 1651 // This method for checking for cyclic structures was egregiously stolen from 1652 // the ingenious method by @kitcambridge from the Underscore script: 1653 // https://github.com/documentcloud/underscore/issues/240 1654 if (!stack) { 1655 stack = []; 1656 } 1657 1658 if (sortedKeys) { 1659 iterateWithSortedKeys(); 1660 } else { 1661 forEachProperty(obj, next); 1662 } 1663 } 1664 1665 1666 // Array helpers 1667 1668 function isArrayIndex(n) { 1669 return n >>> 0 == n && n != 0xFFFFFFFF; 1670 } 1671 1672 function iterateOverSparseArray(arr, fn, fromIndex, loop) { 1673 var indexes = getSparseArrayIndexes(arr, fromIndex, loop), index; 1674 for (var i = 0, len = indexes.length; i < len; i++) { 1675 index = indexes[i]; 1676 fn.call(arr, arr[index], index, arr); 1677 } 1678 return arr; 1679 } 1680 1681 // It's unclear whether or not sparse arrays qualify as "simple enumerables". 1682 // If they are not, however, the wrapping function will be deoptimized, so 1683 // isolate here (also to share between es5 and array modules). 1684 function getSparseArrayIndexes(arr, fromIndex, loop, fromRight) { 1685 var indexes = [], i; 1686 for (i in arr) { 1687 if (isArrayIndex(i) && (loop || (fromRight ? i <= fromIndex : i >= fromIndex))) { 1688 indexes.push(+i); 1689 } 1690 } 1691 indexes.sort(function(a, b) { 1692 var aLoop = a > fromIndex; 1693 var bLoop = b > fromIndex; 1694 if (aLoop !== bLoop) { 1695 return aLoop ? -1 : 1; 1696 } 1697 return a - b; 1698 }); 1699 return indexes; 1700 } 1701 1702 function getEntriesForIndexes(obj, find, loop, isString) { 1703 var result, length = obj.length; 1704 if (!isArray(find)) { 1705 return entryAtIndex(obj, find, length, loop, isString); 1706 } 1707 result = new Array(find.length);
1708 forEach(find, function(index, i) { 1709 result[i] = entryAtIndex(obj, index, length, loop, isString); 1710 }); 1711 return result; 1712 } 1713 1714 function getNormalizedIndex(index, length, loop) { 1715 if (index && loop) { 1716 index = index % length; 1717 } 1718 if (index < 0) index = length + index; 1719 return index; 1720 } 1721 1722 function entryAtIndex(obj, index, length, loop, isString) { 1723 index = getNormalizedIndex(index, length, loop); 1724 return isString ? obj.charAt(index) : obj[index]; 1725 } 1726 1727 function mapWithShortcuts(el, f, context, mapArgs) { 1728 if (!f) { 1729 return el; 1730 } else if (f.apply) { 1731 return f.apply(context, mapArgs || []); 1732 } else if (isArray(f)) { 1733 return f.map(function(m) { 1734 return mapWithShortcuts(el, m, context, mapArgs); 1735 }); 1736 } else if (isFunction(el[f])) { 1737 return el[f].call(el); 1738 } else { 1739 return deepGetProperty(el, f); 1740 } 1741 } 1742 1743 function spaceSplit(str) { 1744 return str.split(' '); 1745 } 1746 1747 function commaSplit(str) { 1748 return str.split(HALF_WIDTH_COMMA); 1749 } 1750 1751 function periodSplit(str) { 1752 return str.split(HALF_WIDTH_PERIOD); 1753 } 1754 1755 function forEach(arr, fn) { 1756 for (var i = 0, len = arr.length; i < len; i++) { 1757 if (!(i in arr)) { 1758 return iterateOverSparseArray(arr, fn, i); 1759 } 1760 fn(arr[i], i); 1761 } 1762 } 1763 1764 function filter(arr, fn) { 1765 var result = []; 1766 for (var i = 0, len = arr.length; i < len; i++) { 1767 var el = arr[i]; 1768 if (i in arr && fn(el, i)) { 1769 result.push(el); 1770 } 1771 } 1772 return result; 1773 } 1774 1775 function map(arr, fn) { 1776 // perf: Not using fixed array len here as it may be sparse. 1777 var result = []; 1778 for (var i = 0, len = arr.length; i < len; i++) { 1779 if (i in arr) { 1780 result.push(fn(arr[i], i)); 1781 } 1782 } 1783 return result; 1784 } 1785 1786 function indexOf(arr, el) { 1787 for (var i = 0, len = arr.length; i < len; i++) { 1788 if (i in arr && arr[i] === el) return i; 1789 } 1790 return -1; 1791 } 1792 1793 // Number helpers 1794 1795 var trunc = Math.trunc || function(n) { 1796 if (n === 0 || !isFinite(n)) return n; 1797 return n < 0 ? ceil(n) : floor(n); 1798 }; 1799 1800 function isRealNaN(obj) { 1801 // This is only true of NaN 1802 return obj != null && obj !== obj; 1803 } 1804 1805 function withPrecision(val, precision, fn) { 1806 var multiplier = pow(10, abs(precision || 0)); 1807 fn = fn || round; 1808 if (precision < 0) multiplier = 1 / multiplier; 1809 return fn(val * multiplier) / multiplier; 1810 } 1811 1812 function padNumber(num, place, sign, base, replacement) { 1813 var str = abs(num).toString(base || 10); 1814 str = repeatString(replacement || '0', place - str.replace(/\.\d+/, '').length) + str; 1815 if (sign || num < 0) { 1816 str = (num < 0 ? '-' : '+') + str; 1817 } 1818 return str; 1819 } 1820 1821 function getOrdinalSuffix(num) { 1822 if (num >= 11 && num <= 13) { 1823 return 'th'; 1824 } else { 1825 switch (num % 10) { 1826 case 1: return 'st'; 1827 case 2: return 'nd'; 1828 case 3: return 'rd'; 1829 default: return 'th'; 1830 } 1831 } 1832 } 1833 1834 // Fullwidth number helpers 1835 var fullWidthNumberReg, fullWidthNumberMap, fullWidthNumbers; 1836 1837 function buildFullWidthNumber() { 1838 var fwp = FULL_WIDTH_PERIOD, hwp = HALF_WIDTH_PERIOD, hwc = HALF_WIDTH_COMMA, fwn = ''; 1839 fullWidthNumberMap = {}; 1840 for (var i = 0, digit; i <= 9; i++) { 1841 digit = chr(i + FULL_WIDTH_ZERO); 1842 fwn += digit; 1843 fullWidthNumberMap[digit] = chr(i + HALF_WIDTH_ZERO); 1844 } 1845 fullWidthNumberMap[hwc] = ''; 1846 fullWidthNumberMap[fwp] = hwp; 1847 // Mapping this to itself to capture it easily 1848 // in stringToNumber to detect decimals later. 1849 fullWidthNumberMap[hwp] = hwp; 1850 fullWidthNumberReg = allCharsReg(fwn + fwp + hwc + hwp); 1851 fullWidthNumbers = fwn; 1852 } 1853 1854 // Takes into account full-width characters, commas, and decimals. 1855 function stringToNumber(str, base) { 1856 var sanitized, isDecimal; 1857 sanitized = str.replace(fullWidthNumberReg, function(chr) { 1858 var replacement = getOwn(fullWidthNumberMap, chr);
1859 if (replacement === HALF_WIDTH_PERIOD) { 1860 isDecimal = true; 1861 } 1862 return replacement; 1863 }); 1864 return isDecimal ? parseFloat(sanitized) : parseInt(sanitized, base || 10); 1865 } 1866 1867 // Math aliases 1868 var abs = Math.abs, 1869 pow = Math.pow, 1870 min = Math.min, 1871 max = Math.max, 1872 ceil = Math.ceil, 1873 floor = Math.floor, 1874 round = Math.round; 1875 1876 1877 // String helpers 1878 1879 var chr = String.fromCharCode; 1880 1881 function trim(str) { 1882 return str.trim(); 1883 } 1884 1885 function repeatString(str, num) { 1886 var result = ''; 1887 str = str.toString(); 1888 while (num > 0) { 1889 if (num & 1) { 1890 result += str; 1891 } 1892 if (num >>= 1) { 1893 str += str; 1894 } 1895 } 1896 return result; 1897 } 1898 1899 function simpleCapitalize(str) { 1900 return str.charAt(0).toUpperCase() + str.slice(1); 1901 } 1902 1903 function createFormatMatcher(bracketMatcher, percentMatcher, precheck) { 1904 1905 var reg = STRING_FORMAT_REG; 1906 var compileMemoized = memoizeFunction(compile); 1907 1908 function getToken(format, match) { 1909 var get, token, literal, fn; 1910 var bKey = match[2]; 1911 var pLit = match[3]; 1912 var pKey = match[5]; 1913 if (match[4] && percentMatcher) { 1914 token = pKey; 1915 get = percentMatcher; 1916 } else if (bKey) { 1917 token = bKey; 1918 get = bracketMatcher; 1919 } else if (pLit && percentMatcher) { 1920 literal = pLit; 1921 } else { 1922 literal = match[1] || match[0]; 1923 } 1924 if (get) { 1925 assertPassesPrecheck(precheck, bKey, pKey); 1926 fn = function(obj, opt) { 1927 return get(obj, token, opt); 1928 }; 1929 } 1930 format.push(fn || getLiteral(literal)); 1931 } 1932 1933 function getSubstring(format, str, start, end) { 1934 if (end > start) { 1935 var sub = str.slice(start, end); 1936 assertNoUnmatched(sub, OPEN_BRACE); 1937 assertNoUnmatched(sub, CLOSE_BRACE); 1938 format.push(function() { 1939 return sub; 1940 }); 1941 } 1942 } 1943 1944 function getLiteral(str) { 1945 return function() { 1946 return str; 1947 }; 1948 } 1949 1950 function assertPassesPrecheck(precheck, bt, pt) { 1951 if (precheck && !precheck(bt, pt)) { 1952 throw new TypeError('Invalid token ' + (bt || pt) + ' in format string'); 1953 } 1954 } 1955 1956 function assertNoUnmatched(str, chr) { 1957 if (str.indexOf(chr) !== -1) { 1958 throw new TypeError('Unmatched ' + chr + ' in format string'); 1959 } 1960 } 1961 1962 function compile(str) { 1963 var format = [], lastIndex = 0, match; 1964 reg.lastIndex = 0; 1965 while (match = reg.exec(str)) { 1966 getSubstring(format, str, lastIndex, match.index); 1967 getToken(format, match); 1968 lastIndex = reg.lastIndex; 1969 } 1970 getSubstring(format, str, lastIndex, str.length); 1971 return format; 1972 } 1973 1974 return function(str, obj, opt) { 1975 var format = compileMemoized(str), result = ''; 1976 for (var i = 0; i < format.length; i++) { 1977 result += format[i](obj, opt); 1978 } 1979 return result; 1980 }; 1981 } 1982 1983 // Inflection helper 1984 1985 var Inflections = {}; 1986 1987 function getAcronym(str) { 1988 return Inflections.acronyms && Inflections.acronyms.find(str); 1989 } 1990 1991 function getHumanWord(str) { 1992 return Inflections.human && Inflections.human.find(str); 1993 } 1994 1995 function runHumanRules(str) { 1996 return Inflections.human && Inflections.human.runRules(str) || str; 1997 } 1998 1999 // RegExp helpers 2000 2001 function allCharsReg(src) { 2002 return RegExp('[' + src + ']', 'g'); 2003 } 2004 2005 function getRegExpFlags(reg, add) { 2006 var flags = ''; 2007 add = add || ''; 2008 function checkFlag(prop, flag) { 2009 if (prop || add.indexOf(flag) > -1) { 2010 flags += flag; 2011 } 2012 } 2013 checkFlag(reg.global, 'g'); 2014 checkFlag(reg.ignoreCase, 'i'); 2015 checkFlag(reg.multiline, 'm'); 2016 checkFlag(reg.sticky, 'y'); 2017 return flags; 2018 } 2019 2020 function escapeRegExp(str) { 2021 if (!isString(str)) str = String(str); 2022 return str.replace(/([\\\/\'*+?|()\[\]{}.^$-])/g, '\\$1'); 2023 } 2024 2025 // Date helpers 2026 2027 var _utc = privatePropertyAccessor('utc'); 2028 2029 function callDateGet(d, method) { 2030 return d['get' + (_utc(d) ? 'UTC' : '') + method](); 2031 } 2032 2033 function callDateSet(d, method, value, safe) { 2034 // "Safe" denotes not setting the date if the value is the same as what is 2035 // currently set. In theory this should be a noop, however it will cause 2036 // timezone shifts when in the middle of a DST fallback. This is unavoidable 2037 // as the notation itself is ambiguous (i.e. there are two "1:00ams" on 2038 // November 1st, 2015 in northern hemisphere timezones that follow DST), 2039 // however when advancing or rewinding dates this can throw off calculations 2040 // so avoiding this unintentional shifting on an opt-in basis. 2041 if (safe && value === callDateGet(d, method, value)) { 2042 return; 2043 } 2044 d['set' + (_utc(d) ? 'UTC' : '') + method](value); 2045 } 2046 2047 // Memoization helpers 2048 2049 var INTERNAL_MEMOIZE_LIMIT = 1000; 2050 2051 // Note that attemps to consolidate this with Function#memoize 2052 // ended up clunky as that is also serializing arguments. Separating 2053 // these implementations turned out to be simpler. 2054 function memoizeFunction(fn) { 2055 var memo = {}, counter = 0; 2056 2057 return function(key) { 2058 if (hasOwn(memo, key)) { 2059 return memo[key]; 2060 } 2061 if (counter === INTERNAL_MEMOIZE_LIMIT) { 2062 memo = {}; 2063 counter = 0; 2064 } 2065 counter++; 2066 return memo[key] = fn(key); 2067 }; 2068 } 2069 2070 // ES6 helpers 2071 2072 function setToArray(set) { 2073 var arr = new Array(set.size), i = 0; 2074 set.forEach(function(val) { 2075 arr[i++] = val; 2076 }); 2077 return arr; 2078 } 2079 2080 function mapToArray(map) { 2081 var arr = new Array(map.size), i = 0;
2082 map.forEach(function(val, key) { 2083 arr[i++] = [key, val]; 2084 }); 2085 return arr; 2086 } 2087 2088 buildClassChecks(); 2089 buildFullWidthNumber(); 2090 2091 /*** 2092 * @module ES6 2093 * @description Polyfills that provide basic ES6 compatibility. This module 2094 * provides the base for Sugar functionality, but is not a full 2095 * polyfill suite. 2096 * 2097 ***/ 2098 2099 2100 /*** @namespace String ***/ 2101 2102 function getCoercedStringSubject(obj) { 2103 if (obj == null) { 2104 throw new TypeError('String required.'); 2105 } 2106 return String(obj); 2107 } 2108 2109 function getCoercedSearchString(obj) { 2110 if (isRegExp(obj)) { 2111 throw new TypeError(); 2112 } 2113 return String(obj); 2114 } 2115 2116 defineInstancePolyfill(sugarString, { 2117 2118 /*** 2119 * @method includes(search, [pos] = 0) 2120 * @returns Boolean 2121 * @polyfill ES6 2122 * @short Returns true if `search` is contained within the string. 2123 * @extra Search begins at [pos], which defaults to the beginning of the 2124 * string. Sugar enhances this method to allow matching a regex. 2125 * 2126 * @example 2127 * 2128 * 'jumpy'.includes('py') -> true 2129 * 'broken'.includes('ken', 3) -> true 2130 * 'broken'.includes('bro', 3) -> false 2131 * 2132 ***/ 2133 'includes': function(searchString) { 2134 // Force compiler to respect argument length. 2135 var argLen = arguments.length, pos = arguments[1]; 2136 var str = getCoercedStringSubject(this); 2137 searchString = getCoercedSearchString(searchString); 2138 return str.indexOf(searchString, pos) !== -1; 2139 }, 2140 2141 /*** 2142 * @method startsWith(search, [pos] = 0) 2143 * @returns Boolean 2144 * @polyfill ES6 2145 * @short Returns true if the string starts with substring `search`. 2146 * @extra Search begins at [pos], which defaults to the entire string length. 2147 * 2148 * @example 2149 * 2150 * 'hello'.startsWith('hell') -> true 2151 * 'hello'.startsWith('HELL') -> false 2152 * 'hello'.startsWith('ell', 1) -> true 2153 * 2154 ***/ 2155 'startsWith': function(searchString) { 2156 // Force compiler to respect argument length. 2157 var argLen = arguments.length, position = arguments[1]; 2158 var str, start, pos, len, searchLength; 2159 str = getCoercedStringSubject(this); 2160 searchString = getCoercedSearchString(searchString); 2161 pos = +position || 0; 2162 len = str.length; 2163 start = min(max(pos, 0), len); 2164 searchLength = searchString.length; 2165 if (searchLength + start > len) { 2166 return false; 2167 } 2168 if (str.substr(start, searchLength) === searchString) { 2169 return true; 2170 } 2171 return false; 2172 }, 2173 2174 /*** 2175 * @method endsWith(search, [pos] = length) 2176 * @returns Boolean 2177 * @polyfill ES6 2178 * @short Returns true if the string ends with substring `search`. 2179 * @extra Search ends at [pos], which defaults to the entire string length. 2180 * 2181 * @example 2182 * 2183 * 'jumpy'.endsWith('py') -> true 2184 * 'jumpy'.endsWith('MPY') -> false 2185 * 'jumpy'.endsWith('mp', 4) -> false 2186 * 2187 ***/ 2188 'endsWith': function(searchString) { 2189 // Force compiler to respect argument length. 2190 var argLen = arguments.length, endPosition = arguments[1]; 2191 var str, start, end, pos, len, searchLength; 2192 str = getCoercedStringSubject(this); 2193 searchString = getCoercedSearchString(searchString); 2194 len = str.length; 2195 pos = len; 2196 if (isDefined(endPosition)) { 2197 pos = +endPosition || 0; 2198 } 2199 end = min(max(pos, 0), len); 2200 searchLength = searchString.length; 2201 start = end - searchLength; 2202 if (start < 0) { 2203 return false; 2204 } 2205 if (str.substr(start, searchLength) === searchString) { 2206 return true; 2207 } 2208 return false; 2209 }, 2210 2211 /*** 2212 * @method repeat([num] = 0) 2213 * @returns String 2214 * @polyfill ES6 2215 * @short Returns the string repeated [num] times. 2216 * 2217 * @example 2218 * 2219 * 'jumpy'.repeat(2) -> 'jumpyjumpy' 2220 * 'a'.repeat(5) -> 'aaaaa' 2221 * 'a'.repeat(0) -> '' 2222 * 2223 ***/ 2224 'repeat': function(num) { 2225 num = coercePositiveInteger(num); 2226 return repeatString(this, num); 2227 } 2228 2229 });
2230 2231 2232 /*** @namespace Number ***/ 2233 2234 defineStaticPolyfill(sugarNumber, { 2235 2236 /*** 2237 * @method isNaN(value) 2238 * @returns Boolean 2239 * @polyfill ES6 2240 * @static 2241 * @short Returns true only if the number is `NaN`. 2242 * @extra This is differs from the global `isNaN`, which returns true for 2243 * anything that is not a number. 2244 * 2245 * @example 2246 * 2247 * Number.isNaN(NaN) -> true 2248 * Number.isNaN('n') -> false 2249 * 2250 ***/ 2251 'isNaN': function(obj) { 2252 return isRealNaN(obj); 2253 } 2254 2255 }); 2256 2257 2258 /*** @namespace Array ***/ 2259 2260 function getCoercedObject(obj) { 2261 if (obj == null) { 2262 throw new TypeError('Object required.'); 2263 } 2264 return coercePrimitiveToObject(obj); 2265 } 2266 2267 defineStaticPolyfill(sugarArray, { 2268 2269 /*** 2270 * @method from(a, [map], [context]) 2271 * @returns Mixed 2272 * @polyfill ES6 2273 * @static 2274 * @short Creates an array from an array-like object. 2275 * @extra If a function is passed for [map], it will be map each element of 2276 * the array. [context] is the `this` object if passed. 2277 * 2278 * @callback map 2279 * 2280 * el The element of the current iteration. 2281 * i The index of the current iteration. 2282 * arr A reference to the array. 2283 * 2284 * @example 2285 * 2286 * Array.from({0:'a',1:'b',length:2}); -> ['a','b'] 2287 * 2288 ***/ 2289 'from': function(a) { 2290 // Force compiler to respect argument length. 2291 var argLen = arguments.length, map = arguments[1], context = arguments[2]; 2292 var len, arr; 2293 if (isDefined(map)) { 2294 assertCallable(map); 2295 } 2296 a = getCoercedObject(a); 2297 len = trunc(max(0, a.length || 0)); 2298 if (!isArrayIndex(len)) { 2299 throw new RangeError('Invalid array length'); 2300 } 2301 if (isFunction(this)) { 2302 arr = new this(len); 2303 arr.length = len; 2304 } else { 2305 arr = new Array(len); 2306 } 2307 for (var i = 0; i < len; i++) { 2308 setProperty(arr, i, isDefined(map) ? map.call(context, a[i], i) : a[i], true); 2309 } 2310 return arr; 2311 } 2312 2313 }); 2314 2315 defineInstancePolyfill(sugarArray, { 2316 2317 'find': function(f) { 2318 // Force compiler to respect argument length. 2319 var argLen = arguments.length, context = arguments[1]; 2320 assertCallable(f); 2321 for (var i = 0, len = this.length; i < len; i++) { 2322 if (f.call(context, this[i], i, this)) { 2323 return this[i]; 2324 } 2325 } 2326 }, 2327 2328 'findIndex': function(f) { 2329 // Force compiler to respect argument length. 2330 var argLen = arguments.length, context = arguments[1]; 2331 assertCallable(f); 2332 for (var i = 0, len = this.length; i < len; i++) { 2333 if (f.call(context, this[i], i, this)) { 2334 return i; 2335 } 2336 } 2337 return -1; 2338 } 2339 2340 }); 2341 2342 /*** 2343 * @module ES7 2344 * @description Polyfills that provide basic ES7 compatibility. This module 2345 * provides the base for Sugar functionality, but is not a full 2346 * polyfill suite. 2347 * 2348 ***/ 2349 2350 2351 /*** @namespace Array ***/ 2352 2353 function sameValueZero(a, b) { 2354 if (isRealNaN(a)) { 2355 return isRealNaN(b); 2356 } 2357 return a === b ? a !== 0 || 1 / a === 1 / b : false; 2358 } 2359 2360 defineInstancePolyfill(sugarArray, { 2361 2362 /*** 2363 * @method includes(search, [fromIndex] = 0) 2364 * @returns Boolean 2365 * @polyfill ES7 2366 * @short Returns true if `search` is contained within the array. 2367 * @extra Search begins at [fromIndex], which defaults to the beginning of the 2368 * array. 2369 * 2370 * @example 2371 * 2372 * [1,2,3].includes(2) -> true 2373 * [1,2,3].includes(4) -> false 2374 * [1,2,3].includes(2, 3) -> false 2375 * 2376 ***/ 2377 'includes': function(search) { 2378 // Force compiler to respect argument length. 2379 var argLen = arguments.length, fromIndex = arguments[1]; 2380 var arr = this, len; 2381 if (isString(arr)) return arr.includes(search, fromIndex); 2382 fromIndex = fromIndex ? fromIndex.valueOf() : 0; 2383 len = arr.length; 2384 if (fromIndex < 0) { 2385 fromIndex = max(0, fromIndex + len); 2386 } 2387 for (var i = fromIndex; i < len; i++) { 2388 if (sameValueZero(search, arr[i])) { 2389 return true; 2390 } 2391 } 2392 return false; 2393 } 2394 2395 });
2396 2397 /*** 2398 * @module Date 2399 * @description Date parsing and formatting, relative formats, number shortcuts, 2400 * and locale support with default English locales. 2401 * 2402 ***/ 2403 2404 var DATE_OPTIONS = { 2405 'newDateInternal': defaultNewDate 2406 }; 2407 2408 var LOCALE_ARRAY_FIELDS = [ 2409 'months', 'weekdays', 'units', 'numerals', 'placeholders', 2410 'articles', 'tokens', 'timeMarkers', 'ampm', 'timeSuffixes', 2411 'parse', 'timeParse', 'timeFrontParse', 'modifiers' 2412 ]; 2413 2414 // Regex for stripping Timezone Abbreviations 2415 var TIMEZONE_ABBREVIATION_REG = /(\w{3})[()\s\d]*$/; 2416 2417 // One minute in milliseconds 2418 var MINUTES = 60 * 1000; 2419 2420 // Date unit indexes 2421 var HOURS_INDEX = 3, 2422 DAY_INDEX = 4, 2423 WEEK_INDEX = 5, 2424 MONTH_INDEX = 6, 2425 YEAR_INDEX = 7; 2426 2427 // ISO Defaults 2428 var ISO_FIRST_DAY_OF_WEEK = 1, 2429 ISO_FIRST_DAY_OF_WEEK_YEAR = 4; 2430 2431 var ParsingTokens = { 2432 'yyyy': { 2433 param: 'year', 2434 src: '\\d{4}' 2435 }, 2436 'MM': { 2437 param: 'month', 2438 src: '[01]?\\d' 2439 }, 2440 'dd': { 2441 param: 'date', 2442 src: '[0123]?\\d' 2443 }, 2444 'hh': { 2445 param: 'hour', 2446 src: '[0-2]?\\d' 2447 }, 2448 'mm': { 2449 param: 'minute', 2450 src: '[0-5]\\d' 2451 }, 2452 'ss': { 2453 param: 'second', 2454 src: '[0-5]\\d(?:[,.]\\d+)?' 2455 }, 2456 'yy': { 2457 param: 'year', 2458 src: '\\d{2}' 2459 }, 2460 'y': { 2461 param: 'year', 2462 src: '\\d' 2463 }, 2464 'yearSign': { 2465 src: '[+-]', 2466 sign: true 2467 }, 2468 'tzHour': { 2469 src: '[0-1]\\d' 2470 }, 2471 'tzMinute': { 2472 src: '[0-5]\\d' 2473 }, 2474 'tzSign': { 2475 src: '[+â-]', 2476 sign: true 2477 }, 2478 'ihh': { 2479 param: 'hour', 2480 src: '[0-2]?\\d(?:[,.]\\d+)?' 2481 }, 2482 'imm': { 2483 param: 'minute', 2484 src: '[0-5]\\d(?:[,.]\\d+)?' 2485 }, 2486 'GMT': { 2487 param: 'utc', 2488 src: 'GMT', 2489 val: 1 2490 }, 2491 'Z': { 2492 param: 'utc', 2493 src: 'Z', 2494 val: 1 2495 }, 2496 'timestamp': { 2497 src: '\\d+' 2498 } 2499 }; 2500 2501 var LocalizedParsingTokens = { 2502 'year': { 2503 base: 'yyyy', 2504 requiresSuffix: true 2505 }, 2506 'month': { 2507 base: 'MM', 2508 requiresSuffix: true 2509 }, 2510 'date': { 2511 base: 'dd', 2512 requiresSuffix: true 2513 }, 2514 'hour': { 2515 base: 'hh', 2516 requiresSuffixOr: ':' 2517 }, 2518 'minute': { 2519 base: 'mm' 2520 }, 2521 'second': { 2522 base: 'ss' 2523 }, 2524 'num': { 2525 src: '\\d+', 2526 requiresNumerals: true 2527 } 2528 }; 2529 2530 var CoreParsingFormats = [ 2531 { 2532 // 12-1978 2533 // 08-1978 (MDY) 2534 src: '{MM}[-.\\/]{yyyy}' 2535 }, 2536 { 2537 // 12/08/1978 2538 // 08/12/1978 (MDY) 2539 time: true, 2540 src: '{dd}[-.\\/]{MM}(?:[-.\\/]{yyyy|yy|y})?', 2541 mdy: '{MM}[-.\\/]{dd}(?:[-.\\/]{yyyy|yy|y})?' 2542 }, 2543 { 2544 // 1975-08-25 2545 time: true, 2546 src: '{yyyy}[-.\\/]{MM}(?:[-.\\/]{dd})?' 2547 }, 2548 { 2549 // .NET JSON 2550 src: '\\\\/Date\\({timestamp}
2550(?:[+-]\\d{4,4})?\\)\\\\/' 2551 }, 2552 { 2553 // ISO-8601 2554 src: '{yearSign?}{yyyy}(?:-?{MM}(?:-?{dd}(?:T{ihh}(?::?{imm}(?::?{ss})?)?)?)?)?{tzOffset?}' 2555 } 2556 ]; 2557 2558 var CoreOutputFormats = { 2559 'ISO8601': '{yyyy}-{MM}-{dd}T{HH}:{mm}:{ss}.{SSS}{Z}', 2560 'RFC1123': '{Dow}, {dd} {Mon} {yyyy} {HH}:{mm}:{ss} {ZZ}', 2561 'RFC1036': '{Weekday}, {dd}-{Mon}-{yy} {HH}:{mm}:{ss} {ZZ}' 2562 }; 2563 2564 var FormatTokensBase = [ 2565 { 2566 ldml: 'Dow', 2567 strf: 'a', 2568 lowerToken: 'dow', 2569 get: function(d, localeCode) { 2570 return localeManager.get(localeCode).getWeekdayName(getWeekday(d), 2); 2571 } 2572 }, 2573 { 2574 ldml: 'Weekday', 2575 strf: 'A', 2576 lowerToken: 'weekday', 2577 allowAlternates: true, 2578 get: function(d, localeCode, alternate) { 2579 return localeManager.get(localeCode).getWeekdayName(getWeekday(d), alternate); 2580 } 2581 }, 2582 { 2583 ldml: 'Mon', 2584 strf: 'b h', 2585 lowerToken: 'mon', 2586 get: function(d, localeCode) { 2587 return localeManager.get(localeCode).getMonthName(getMonth(d), 2); 2588 } 2589 }, 2590 { 2591 ldml: 'Month', 2592 strf: 'B', 2593 lowerToken: 'month', 2594 allowAlternates: true, 2595 get: function(d, localeCode, alternate) { 2596 return localeManager.get(localeCode).getMonthName(getMonth(d), alternate); 2597 } 2598 }, 2599 { 2600 strf: 'C', 2601 get: function(d) { 2602 return getYear(d).toString().slice(0, 2); 2603 } 2604 }, 2605 { 2606 ldml: 'd date day', 2607 strf: 'd', 2608 strfPadding: 2, 2609 ldmlPaddedToken: 'dd', 2610 ordinalToken: 'do', 2611 get: function(d) { 2612 return getDate(d); 2613 } 2614 }, 2615 { 2616 strf: 'e', 2617 get: function(d) { 2618 return padNumber(getDate(d), 2, false, 10, ' '); 2619 } 2620 }, 2621 { 2622 ldml: 'H 24hr', 2623 strf: 'H', 2624 strfPadding: 2, 2625 ldmlPaddedToken: 'HH', 2626 get: function(d) { 2627 return getHours(d); 2628 } 2629 }, 2630 { 2631 ldml: 'h hours 12hr', 2632 strf: 'I', 2633 strfPadding: 2, 2634 ldmlPaddedToken: 'hh', 2635 get: function(d) { 2636 return getHours(d) % 12 || 12; 2637 } 2638 }, 2639 { 2640 ldml: 'D', 2641 strf: 'j', 2642 strfPadding: 3, 2643 ldmlPaddedToken: 'DDD', 2644 get: function(d) { 2645 var s = setUnitAndLowerToEdge(cloneDate(d), MONTH_INDEX); 2646 return getDaysSince(d, s) + 1; 2647 } 2648 }, 2649 { 2650 ldml: 'M', 2651 strf: 'm', 2652 strfPadding: 2, 2653 ordinalToken: 'Mo', 2654 ldmlPaddedToken: 'MM', 2655 get: function(d) { 2656 return getMonth(d) + 1; 2657 } 2658 }, 2659 { 2660 ldml: 'm minutes', 2661 strf: 'M', 2662 strfPadding: 2, 2663 ldmlPaddedToken: 'mm', 2664 get: function(d) { 2665 return callDateGet(d, 'Minutes'); 2666 } 2667 }, 2668 { 2669 ldml: 'Q', 2670 get: function(d) { 2671 return ceil((getMonth(d) + 1) / 3); 2672 } 2673 }, 2674 { 2675 ldml: 'TT', 2676 strf: 'p', 2677 get: function(d, localeCode) { 2678 return getMeridiemToken(d, localeCode); 2679 } 2680 }, 2681 { 2682 ldml: 'tt', 2683 strf: 'P', 2684 get: function(d, localeCode) { 2685 return getMeridiemToken(d, localeCode).toLowerCase(); 2686 } 2687 }, 2688 { 2689 ldml: 'T', 2690 lowerToken: 't', 2691 get: function(d, localeCode) { 2692 return getMeridiemToken(d, localeCode).charAt(0); 2693 } 2694 }, 2695 { 2696 ldml: 's seconds', 2697 strf: 'S', 2698 strfPadding: 2, 2699 ldmlPaddedToken: 'ss', 2700 get: function(d) { 2701 return callDateGet(d, 'Seconds'); 2702 } 2703 }, 2704 { 2705 ldml: 'S ms', 2706 strfPadding: 3, 2707 ldmlPaddedToken: 'SSS', 2708 get: function(d) { 2709 return callDateGet(d, 'Milliseconds'); 2710 } 2711 }, 2712 { 2713 ldml: 'e', 2714 strf: 'u', 2715 ordinalToken: 'eo', 2716 get: function(d) { 2717 return getWeekday(d) || 7; 2718 } 2719 }, 2720 { 2721 strf: 'U', 2722 strfPadding: 2, 2723 get: function(d) { 2724 // Sunday first, 0-53 2725 return getWeekNumber(d, false, 0); 2726 } 2727 }, 2728 { 2729 ldml: 'W', 2730 strf: 'V', 2731 strfPadding: 2, 2732 ordinalToken: 'Wo', 2733 ldmlPaddedToken: 'WW', 2734 get: function(d) {
2735 // Monday first, 1-53 (ISO8601) 2736 return getWeekNumber(d, true); 2737 } 2738 }, 2739 { 2740 strf: 'w', 2741 get: function(d) { 2742 return getWeekday(d); 2743 } 2744 }, 2745 { 2746 ldml: 'w', 2747 ordinalToken: 'wo', 2748 ldmlPaddedToken: 'ww', 2749 get: function(d, localeCode) { 2750 // Locale dependent, 1-53 2751 var loc = localeManager.get(localeCode), 2752 dow = loc.getFirstDayOfWeek(localeCode), 2753 doy = loc.getFirstDayOfWeekYear(localeCode); 2754 return getWeekNumber(d, true, dow, doy); 2755 } 2756 }, 2757 { 2758 strf: 'W', 2759 strfPadding: 2, 2760 get: function(d) { 2761 // Monday first, 0-53 2762 return getWeekNumber(d, false); 2763 } 2764 }, 2765 { 2766 ldmlPaddedToken: 'gggg', 2767 ldmlTwoDigitToken: 'gg', 2768 get: function(d, localeCode) { 2769 return getWeekYear(d, localeCode); 2770 } 2771 }, 2772 { 2773 strf: 'G', 2774 strfPadding: 4, 2775 strfTwoDigitToken: 'g', 2776 ldmlPaddedToken: 'GGGG', 2777 ldmlTwoDigitToken: 'GG', 2778 get: function(d, localeCode) { 2779 return getWeekYear(d, localeCode, true); 2780 } 2781 }, 2782 { 2783 ldml: 'year', 2784 ldmlPaddedToken: 'yyyy', 2785 ldmlTwoDigitToken: 'yy', 2786 strf: 'Y', 2787 strfPadding: 4, 2788 strfTwoDigitToken: 'y', 2789 get: function(d) { 2790 return getYear(d); 2791 } 2792 }, 2793 { 2794 ldml: 'ZZ', 2795 strf: 'z', 2796 get: function(d) { 2797 return getUTCOffset(d); 2798 } 2799 }, 2800 { 2801 ldml: 'X', 2802 get: function(d) { 2803 return trunc(d.getTime() / 1000); 2804 } 2805 }, 2806 { 2807 ldml: 'x', 2808 get: function(d) { 2809 return d.getTime(); 2810 } 2811 }, 2812 { 2813 ldml: 'Z', 2814 get: function(d) { 2815 return getUTCOffset(d, true); 2816 } 2817 }, 2818 { 2819 ldml: 'z', 2820 strf: 'Z', 2821 get: function(d) { 2822 // Note that this is not accurate in all browsing environments! 2823 // https://github.com/moment/moment/issues/162 2824 // It will continue to be supported for Node and usage with the 2825 // understanding that it may be blank. 2826 var match = d.toString().match(TIMEZONE_ABBREVIATION_REG); 2827 return match ? match[1] : ''; 2828 } 2829 }, 2830 { 2831 strf: 'D', 2832 alias: '%m/%d/%y' 2833 }, 2834 { 2835 strf: 'F', 2836 alias: '%Y-%m-%d' 2837 }, 2838 { 2839 strf: 'r', 2840 alias: '%I:%M:%S %p' 2841 }, 2842 { 2843 strf: 'R', 2844 alias: '%H:%M' 2845 }, 2846 { 2847 strf: 'T', 2848 alias: '%H:%M:%S' 2849 }, 2850 { 2851 strf: 'x', 2852 alias: '{short}' 2853 }, 2854 { 2855 strf: 'X', 2856 alias: '{time}' 2857 }, 2858 { 2859 strf: 'c', 2860 alias: '{stamp}' 2861 } 2862 ]; 2863 2864 var DateUnits = [ 2865 { 2866 name: 'millisecond', 2867 method: 'Milliseconds', 2868 multiplier: 1, 2869 start: 0, 2870 end: 999 2871 }, 2872 { 2873 name: 'second', 2874 method: 'Seconds', 2875 multiplier: 1000, 2876 start: 0, 2877 end: 59 2878 }, 2879 { 2880 name: 'minute', 2881 method: 'Minutes', 2882 multiplier: 60 * 1000, 2883 start: 0, 2884 end: 59 2885 }, 2886 { 2887 name: 'hour', 2888 method: 'Hours', 2889 multiplier: 60 * 60 * 1000, 2890 start: 0, 2891 end: 23 2892 }, 2893 { 2894 name: 'day', 2895 alias: 'date', 2896 method: 'Date', 2897 ambiguous: true, 2898 multiplier: 24 * 60 * 60 * 1000, 2899 start: 1, 2900 end: function(d) { 2901 return getDaysInMonth(d); 2902 } 2903 }, 2904 { 2905 name: 'week', 2906 method: 'ISOWeek', 2907 ambiguous: true, 2908 multiplier: 7 * 24 * 60 * 60 * 1000 2909 }, 2910 { 2911 name: 'month', 2912 method: 'Month', 2913 ambiguous: true, 2914 multiplier: 30.4375 * 24 * 60 * 60 * 1000, 2915 start: 0, 2916 end: 11 2917 }, 2918 { 2919 name: 'year', 2920 method: 'FullYear', 2921 ambiguous: true, 2922 multiplier: 365.25 * 24 * 60 * 60 * 1000, 2923 start: 0 2924 } 2925 ]; 2926 2927 /*** 2928 * @method getOption(name) 2929 * @returns Mixed 2930 * @accessor 2931 * @short Gets an option used interally by Date. 2932 * @example 2933 * 2934 * Sugar.Date.getOption('newDateInternal'); 2935 * 2936 * @param {string} name 2937 * 2938 *** 2939 * @method setOption(name, value) 2940 * @accessor 2941 * @short Sets an option used interally by Date. 2942 * @extra If `value` is `null`, the default value will be restored. 2943 * @options 2944 * 2945 * newDateInternal Sugar's internal date constructor. Date methods often 2946 * construct a `new Date()` internally as a reference point 2947 * (`isToday`, relative formats like `tomorrow`, etc). This 2948 * can be overridden if you need it to be something else. 2949 * Most commonly, this allows you to return a shifted date 2950 * to simulate a specific timezone, as dates in Javascript 2951 * are always local. 2952 * 2953 * @example 2954 * 2955 * Sugar.Date.setOption('newDateInternal', function() { 2956 * var d = new Date(), offset; 2957 * offset = (d.getTimezoneOffset() - 600) * 60 * 1000; // Hawaii time! 2958 * d.setTime(d.getTime() + offset); 2959 * return d; 2960 * }); 2961 * 2962 * @signature setOption(options) 2963 * @param {DateOptions} options 2964 * @param {string} name 2965 * @param {any} value 2966 * @option {Function} newDateInternal 2967 * 2968 ***/ 2969 var _dateOptions = defineOptionsAccessor(sugarDate, DATE_OPTIONS); 2970 2971 function setDateChainableConstructor() { 2972 setChainableConstructor(sugarDate, createDate); 2973 } 2974 2975 // General helpers 2976 2977 function getNewDate() { 2978 return _dateOptions('newDateInternal')(); 2979 } 2980 2981 function defaultNewDate() { 2982 return new Date; 2983 } 2984 2985 function cloneDate(d) { 2986 // Rhino environments have a bug where new Date(d) truncates 2987 // milliseconds so need to call getTime() here. 2988 var clone = new Date(d.getTime()); 2989 _utc(clone, !!_utc(d)); 2990 return clone; 2991 } 2992 2993 function dateIsValid(d) { 2994 return !isNaN(d.getTime()); 2995 } 2996 2997 function assertDateIsValid(d) { 2998 if (!dateIsValid(d)) {
2999 throw new TypeError('Date is not valid'); 3000 } 3001 } 3002 3003 function getHours(d) { 3004 return callDateGet(d, 'Hours'); 3005 } 3006 3007 function getWeekday(d) { 3008 return callDateGet(d, 'Day'); 3009 } 3010 3011 function getDate(d) { 3012 return callDateGet(d, 'Date'); 3013 } 3014 3015 function getMonth(d) { 3016 return callDateGet(d, 'Month'); 3017 } 3018 3019 function getYear(d) { 3020 return callDateGet(d, 'FullYear'); 3021 } 3022 3023 function setDate(d, val) { 3024 callDateSet(d, 'Date', val); 3025 } 3026 3027 function setMonth(d, val) { 3028 callDateSet(d, 'Month', val); 3029 } 3030 3031 function setYear(d, val) { 3032 callDateSet(d, 'FullYear', val); 3033 } 3034 3035 function getDaysInMonth(d) { 3036 return 32 - callDateGet(new Date(getYear(d), getMonth(d), 32), 'Date'); 3037 } 3038 3039 function setWeekday(d, dow, dir) { 3040 if (!isNumber(dow)) return; 3041 var currentWeekday = getWeekday(d); 3042 if (dir) { 3043 // Allow a "direction" parameter to determine whether a weekday can 3044 // be set beyond the current weekday in either direction. 3045 var ndir = dir > 0 ? 1 : -1; 3046 var offset = dow % 7 - currentWeekday; 3047 if (offset && offset / abs(offset) !== ndir) { 3048 dow += 7 * ndir; 3049 } 3050 } 3051 setDate(d, getDate(d) + dow - currentWeekday); 3052 return d.getTime(); 3053 } 3054 3055 // Normal callDateSet method with ability 3056 // to handle ISOWeek setting as well. 3057 function callDateSetWithWeek(d, method, value, safe) { 3058 if (method === 'ISOWeek') { 3059 setISOWeekNumber(d, value); 3060 } else { 3061 callDateSet(d, method, value, safe); 3062 } 3063 } 3064 3065 // UTC helpers 3066 3067 function isUTC(d) { 3068 return !!_utc(d) || tzOffset(d) === 0; 3069 } 3070 3071 function getUTCOffset(d, iso) { 3072 var offset = _utc(d) ? 0 : tzOffset(d), hours, mins, colon; 3073 colon = iso === true ? ':' : ''; 3074 if (!offset && iso) return 'Z'; 3075 hours = padNumber(trunc(-offset / 60), 2, true); 3076 mins = padNumber(abs(offset % 60), 2); 3077 return hours + colon + mins; 3078 } 3079 3080 function tzOffset(d) { 3081 return d.getTimezoneOffset(); 3082 } 3083 3084 // Argument helpers 3085 3086 function collectDateArguments(args, allowDuration) { 3087 var arg1 = args[0], arg2 = args[1]; 3088 if (allowDuration && isString(arg1)) { 3089 arg1 = getDateParamsFromString(arg1); 3090 } else if (isNumber(arg1) && isNumber(arg2)) { 3091 arg1 = collectDateParamsFromArguments(args); 3092 arg2 = null; 3093 } else { 3094 if (isObjectType(arg1)) { 3095 arg1 = simpleClone(arg1); 3096 } 3097 } 3098 return [arg1, arg2]; 3099 } 3100 3101 function collectDateParamsFromArguments(args) { 3102 var params = {}, index = 0; 3103 walkUnitDown(YEAR_INDEX, function(unit) { 3104 var arg = args[index++]; 3105 if (isDefined(arg)) { 3106 params[unit.name] = arg; 3107 } 3108 }); 3109 return params; 3110 } 3111 3112 function getDateParamsFromString(str) { 3113 var match, num, params = {}; 3114 match = str.match(/^(-?\d*[\d.]\d*)?\s?(\w+?)s?$/i); 3115 if (match) { 3116 if (isUndefined(num)) { 3117 num = +match[1]; 3118 if (isNaN(num)) { 3119 num = 1; 3120 } 3121 } 3122 params[match[2].toLowerCase()] = num; 3123 } 3124 return params; 3125 } 3126 3127 // Iteration helpers 3128 3129 // Years -> Milliseconds 3130 function iterateOverDateUnits(fn, startIndex, endIndex) { 3131 endIndex = endIndex || 0; 3132 if (isUndefined(startIndex)) { 3133 startIndex = YEAR_INDEX; 3134 } 3135 for (var index = startIndex; index >= endIndex; index--) { 3136 if (fn(DateUnits[index], index) === false) { 3137 break; 3138 } 3139 } 3140 } 3141 3142 // Years -> Milliseconds using getLower/Higher methods 3143 function walkUnitDown(unitIndex, fn) { 3144 while (unitIndex >= 0) { 3145 if (fn(DateUnits[unitIndex], unitIndex) === false) { 3146 break; 3147 } 3148 unitIndex = getLowerUnitIndex(unitIndex); 3149 } 3150 } 3151 3152 // Moving lower from specific unit 3153 function getLowerUnitIndex(index) { 3154 if (index === MONTH_INDEX) { 3155 return DAY_INDEX; 3156 } else if (index === WEEK_INDEX) { 3157 return HOURS_INDEX; 3158 } 3159 return index - 1; 3160 } 3161 3162 // Moving higher from specific unit 3163 function getHigherUnitIndex(index) { 3164 return index === DAY_INDEX ? MONTH_INDEX : index + 1; 3165 } 3166 3167 // Years -> Milliseconds checking all date params including "weekday" 3168 function iterateOverDateParams(params, fn, startIndex, endIndex) { 3169 3170 function run(name, unit, i) { 3171 var val = getDateParam(params, name); 3172 if (isDefined(val)) { 3173 fn(name, val, unit, i); 3174 } 3175 } 3176 3177 iterateOverDateUnits(function(unit, i) { 3178 var result = run(unit.name, unit, i); 3179 if (result !== false && i === DAY_INDEX) { 3180 // Check for "weekday", which has a distinct meaning 3181 // in the context of setting a date, but has the same 3182 // meaning as "day" as a unit of time. 3183 result = run('weekday', unit, i); 3184 } 3185 return result; 3186 }, startIndex, endIndex); 3187 3188 } 3189 3190 // Years -> Days 3191 function iterateOverHigherDateParams(params, fn) { 3192 iterateOverDateParams(params, fn, YEAR_INDEX, DAY_INDEX); 3193 } 3194 3195 // Advancing helpers 3196 3197 function advanceDate(d, unit, num, reset) { 3198 var set = {}; 3199 set[unit] = num; 3200 return updateDate(d, set, reset, 1); 3201 } 3202 3203 function advanceDateWithArgs(d, args, dir) { 3204 args = collectDateArguments(args, true); 3205 return updateDate(d, args[0], args[1], dir); 3206 } 3207 3208 // Edge helpers 3209 3210 function resetTime(d) { 3211 return setUnitAndLowerToEdge(d, HOURS_INDEX); 3212 } 3213 3214 function resetLowerUnits(d, unitIndex) { 3215 return setUnitAndLowerToEdge(d, getLowerUnitIndex(unitIndex)); 3216 } 3217 3218 function moveToBeginningOfWeek(d, firstDayOfWeek) { 3219 setWeekday(d, floor((getWeekday(d) - firstDayOfWeek) / 7) * 7 + firstDayOfWeek); 3220 return d; 3221 } 3222 3223 function moveToEndOfWeek(d, firstDayOfWeek) { 3224 var target = firstDayOfWeek - 1; 3225 setWeekday(d, ceil((getWeekday(d) - target) / 7) * 7 + target); 3226 return d; 3227 } 3228 3229 function moveToBeginningOfUnit(d, unitIndex, localeCode) { 3230 if (unitIndex === WEEK_INDEX) { 3231 moveToBeginningOfWeek(d, localeManager.get(localeCode).getFirstDayOfWeek()); 3232 } 3233 return setUnitAndLowerToEdge(d, getLowerUnitIndex(unitIndex)); 3234 } 3235 3236 function moveToEndOfUnit(d, unitIndex, localeCode, stopIndex) { 3237 if (unitIndex === WEEK_INDEX) { 3238 moveToEndOfWeek(d, localeManager.get(localeCode).getFirstDayOfWeek()); 3239 } 3240 return setUnitAndLowerToEdge(d, getLowerUnitIndex(unitIndex), stopIndex, true); 3241 } 3242 3243 function setUnitAndLowerToEdge(d, startIndex, stopIndex, end) { 3244 walkUnitDown(startIndex, function(unit, i) { 3245 var val = end ? unit.end : unit.start; 3246 if (isFunction(val)) { 3247 val = val(d); 3248 } 3249 callDateSet(d, unit.method, val); 3250 return !isDefined(stopIndex) || i > stopIndex; 3251 }); 3252 return d; 3253 } 3254 3255 // Param helpers 3256 3257 function getDateParamKey(params, key) { 3258 return getOwnKey(params, key) || 3259 getOwnKey(params, key + 's') || 3260 (key === 'day' && getOwnKey(params, 'date')); 3261 } 3262 3263 function getDateParam(params, key) { 3264 return getOwn(params, getDateParamKey(params, key)); 3265 } 3266 3267 function deleteDateParam(params, key) { 3268 delete params[getDateParamKey(params, key)]; 3269 } 3270 3271 function getUnitIndexForParamName(name) { 3272 var params = {}, unitIndex; 3273 params[name] = 1; 3274 iterateOverDateParams(params, function(name, val, unit, i) { 3275 unitIndex = i; 3276 return false; 3277 }); 3278 return unitIndex; 3279 } 3280 3281 // Time distance helpers 3282 3283 function getDaysSince(d1, d2) { 3284 return getTimeDistanceForUnit(d1, d2, DateUnits[DAY_INDEX]); 3285 } 3286 3287 function getTimeDistanceForUnit(d1, d2, unit) { 3288 var fwd = d2 > d1, num, tmp; 3289 if (!fwd) { 3290 tmp = d2; 3291 d2 = d1; 3292 d1 = tmp; 3293 } 3294 num = d2 - d1; 3295 if (unit.multiplier > 1) { 3296 num = trunc(num / unit.multiplier); 3297 } 3298 // For higher order with potential ambiguity, use the numeric calculation 3299 // as a starting point, then iterate until we pass the target date. 3300 if (unit.ambiguous) { 3301 d1 = cloneDate(d1); 3302 if (num) { 3303 advanceDate(d1, unit.name, num); 3304 } 3305 while (d1 < d2) { 3306 advanceDate(d1, unit.name, 1); 3307 if (d1 > d2) { 3308 break; 3309 } 3310 num += 1; 3311 } 3312 } 3313 return fwd ? -num : num; 3314 } 3315 3316 // Parsing helpers 3317 3318 function getParsingTokenValue(token, str) { 3319 var val; 3320 if (token.val) { 3321 val = token.val; 3322 } else if (token.sign) { 3323 val = str === '+' ? 1 : -1; 3324 } else if (token.bool) { 3325 val = !!val; 3326 } else { 3327 val = +str.replace(/,/, '.'); 3328 } 3329 if (token.param === 'month') { 3330 val -= 1; 3331 } 3332 return val; 3333 } 3334 3335 function getYearFromAbbreviation(str, d, prefer) { 3336 // Following IETF here, adding 1900 or 2000 depending on the last two digits. 3337 // Note that this makes no accordance for what should happen after 2050, but 3338 // intentionally ignoring this for now. https://www.ietf.org/rfc/rfc2822.txt 3339 var val = +str, delta; 3340 val += val < 50 ? 2000 : 1900; 3341 if (prefer) { 3342 delta = val - getYear(d); 3343 if (delta / abs(delta) !== prefer) { 3344 val += prefer * 100; 3345 } 3346 } 3347 return val; 3348 } 3349 3350 // Week number helpers 3351 3352 function setISOWeekNumber(d, num) { 3353 if (isNumber(num)) { 3354 // Intentionally avoiding updateDate here to prevent circular dependencies. 3355 var isoWeek = cloneDate(d), dow = getWeekday(d); 3356 moveToFirstDayOfWeekYear(isoWeek, ISO_FIRST_DAY_OF_WEEK, ISO_FIRST_DAY_OF_WEEK_YEAR); 3357 setDate(isoWeek, getDate(isoWeek) + 7 * (num - 1)); 3358 setYear(d, getYear(isoWeek)); 3359 setMonth(d, getMonth(isoWeek)); 3360 setDate(d, getDate(isoWeek)); 3361 setWeekday(d, dow || 7); 3362 } 3363 return d.getTime(); 3364 } 3365 3366 function getWeekNumber(d, allowPrevious, firstDayOfWeek, firstDayOfWeekYear) { 3367 var isoWeek, n = 0; 3368 if (isUndefined(firstDayOfWeek)) { 3369 firstDayOfWeek = ISO_FIRST_DAY_OF_WEEK; 3370 } 3371 if (isUndefined(firstDayOfWeekYear)) { 3372 firstDayOfWeekYear = ISO_FIRST_DAY_OF_WEEK_YEAR; 3373 } 3374 // Moving to the end of the week allows for forward year traversal, ie 3375 // Dec 29 2014 is actually week 01 of 2015. 3376 isoWeek = moveToEndOfWeek(cloneDate(d), firstDayOfWeek); 3377 moveToFirstDayOfWeekYear(isoWeek, firstDayOfWeek, firstDayOfWeekYear); 3378 if (allowPrevious && d < isoWeek) { 3379 // If the date is still before the start of the year, then it should be 3380 // the last week of the previous year, ie Jan 1 2016 is actually week 53 3381 // of 2015, so move to the beginning of the week to traverse the year. 3382 isoWeek = moveToBeginningOfWeek(cloneDate(d), firstDayOfWeek); 3383 moveToFirstDayOfWeekYear(isoWeek, firstDayOfWeek, firstDayOfWeekYear); 3384 } 3385 while (isoWeek <= d) { 3386 // Doing a very simple walk to get the week number. 3387 setDate(isoWeek, getDate(isoWeek) + 7); 3388 n++; 3389 } 3390 return n; 3391 } 3392 3393 // Week year helpers 3394 3395 function getWeekYear(d, localeCode, iso) { 3396 var year, month, firstDayOfWeek, firstDayOfWeekYear, week, loc; 3397 year = getYear(d);
3398 month = getMonth(d); 3399 if (month === 0 || month === 11) { 3400 if (!iso) { 3401 loc = localeManager.get(localeCode); 3402 firstDayOfWeek = loc.getFirstDayOfWeek(localeCode); 3403 firstDayOfWeekYear = loc.getFirstDayOfWeekYear(localeCode); 3404 } 3405 week = getWeekNumber(d, false, firstDayOfWeek, firstDayOfWeekYear); 3406 if (month === 0 && week === 0) { 3407 year -= 1; 3408 } else if (month === 11 && week === 1) { 3409 year += 1; 3410 } 3411 } 3412 return year; 3413 } 3414 3415 function moveToFirstDayOfWeekYear(d, firstDayOfWeek, firstDayOfWeekYear) { 3416 setUnitAndLowerToEdge(d, MONTH_INDEX); 3417 setDate(d, firstDayOfWeekYear); 3418 moveToBeginningOfWeek(d, firstDayOfWeek); 3419 } 3420 3421 // Relative helpers 3422 3423 function dateRelative(d, dRelative, arg1, arg2) { 3424 var adu, format, type, localeCode, fn; 3425 assertDateIsValid(d); 3426 if (isFunction(arg1)) { 3427 fn = arg1; 3428 } else { 3429 localeCode = arg1; 3430 fn = arg2; 3431 } 3432 adu = getAdjustedUnitForDate(d, dRelative); 3433 if (fn) { 3434 format = fn.apply(d, adu.concat(localeManager.get(localeCode))); 3435 if (format) { 3436 return dateFormat(d, format, localeCode); 3437 } 3438 } 3439 // Adjust up if time is in ms, as this doesn't 3440 // look very good for a standard relative date. 3441 if (adu[1] === 0) { 3442 adu[1] = 1; 3443 adu[0] = 1; 3444 } 3445 if (dRelative) { 3446 type = 'duration'; 3447 } else if (adu[2] > 0) { 3448 type = 'future'; 3449 } else { 3450 type = 'past'; 3451 } 3452 return localeManager.get(localeCode).getRelativeFormat(adu, type); 3453 } 3454 3455 // Gets an "adjusted date unit" which is a way of representing 3456 // the largest possible meaningful unit. In other words, if passed 3457 // 3600000, this will return an array which represents "1 hour". 3458 function getAdjustedUnit(ms, fn) { 3459 var unitIndex = 0, value = 0; 3460 iterateOverDateUnits(function(unit, i) { 3461 value = abs(fn(unit)); 3462 if (value >= 1) { 3463 unitIndex = i; 3464 return false; 3465 } 3466 }); 3467 return [value, unitIndex, ms]; 3468 } 3469 3470 // Gets the adjusted unit based on simple division by 3471 // date unit multiplier. 3472 function getAdjustedUnitForNumber(ms) { 3473 return getAdjustedUnit(ms, function(unit) { 3474 return trunc(withPrecision(ms / unit.multiplier, 1)); 3475 }); 3476 } 3477 3478 // Gets the adjusted unit using the unitsFromNow methods, 3479 // which use internal date methods that neatly avoid vaguely 3480 // defined units of time (days in month, leap years, etc). 3481 // Reserving dRelative to allow another date to be relative to. 3482 function getAdjustedUnitForDate(d, dRelative) { 3483 var ms; 3484 if (!dRelative) { 3485 dRelative = getNewDate(); 3486 if (d > dRelative) { 3487 // If our date is greater than the one that we got from getNewDate, it 3488 // means that we are finding the unit for a date that is in the future 3489 // relative to now. However, often the incoming date was created in 3490 // the same cycle as our comparison, but our "now" date will have been 3491 // created an instant after it, creating situations where "5 minutes from 3492 // now" becomes "4 minutes from now" in the same tick. To prevent this, 3493 // subtract a buffer of 10ms to compensate. 3494 dRelative = new Date(dRelative.getTime() - 10); 3495 } 3496 } 3497 ms = d - dRelative; 3498 return getAdjustedUnit(ms, function(u) { 3499 return abs(getTimeDistanceForUnit(d, dRelative, u)); 3500 }); 3501 } 3502 3503 // Foramtting helpers 3504 3505 // Formatting tokens 3506 var ldmlTokens, strfTokens; 3507 3508 function dateFormat(d, format, localeCode) { 3509 assertDateIsValid(d); 3510 format = CoreOutputFormats[format] || format || '{long}'; 3511 return dateFormatMatcher(format, d, localeCode); 3512 } 3513 3514 function getMeridiemToken(d, localeCode) { 3515 var hours = getHours(d); 3516 return localeManager.get(localeCode).ampm[trunc(hours / 12)] || ''; 3517 } 3518 3519 function buildDateFormatTokens() { 3520 3521 function addFormats(target, tokens, fn) { 3522 if (tokens) {
3523 forEach(spaceSplit(tokens), function(token) { 3524 target[token] = fn; 3525 }); 3526 } 3527 } 3528 3529 function buildLowercase(get) { 3530 return function(d, localeCode) { 3531 return get(d, localeCode).toLowerCase(); 3532 }; 3533 } 3534 3535 function buildOrdinal(get) { 3536 return function(d, localeCode) { 3537 var n = get(d, localeCode); 3538 return n + localeManager.get(localeCode).getOrdinal(n); 3539 }; 3540 } 3541 3542 function buildPadded(get, padding) { 3543 return function(d, localeCode) { 3544 return padNumber(get(d, localeCode), padding); 3545 }; 3546 } 3547 3548 function buildTwoDigits(get) { 3549 return function(d, localeCode) { 3550 return get(d, localeCode) % 100; 3551 }; 3552 } 3553 3554 function buildAlias(alias) { 3555 return function(d, localeCode) { 3556 return dateFormatMatcher(alias, d, localeCode); 3557 }; 3558 } 3559 3560 function buildAlternates(f) { 3561 for (var n = 1; n <= 5; n++) { 3562 buildAlternate(f, n); 3563 } 3564 } 3565 3566 function buildAlternate(f, n) { 3567 var alternate = function(d, localeCode) { 3568 return f.get(d, localeCode, n); 3569 }; 3570 addFormats(ldmlTokens, f.ldml + n, alternate); 3571 if (f.lowerToken) { 3572 ldmlTokens[f.lowerToken + n] = buildLowercase(alternate); 3573 } 3574 } 3575 3576 function getIdentityFormat(name) { 3577 return function(d, localeCode) { 3578 var loc = localeManager.get(localeCode); 3579 return dateFormatMatcher(loc[name], d, localeCode); 3580 }; 3581 } 3582 3583 ldmlTokens = {}; 3584 strfTokens = {}; 3585 3586 forEach(FormatTokensBase, function(f) { 3587 var get = f.get, getPadded; 3588 if (f.lowerToken) { 3589 ldmlTokens[f.lowerToken] = buildLowercase(get); 3590 } 3591 if (f.ordinalToken) { 3592 ldmlTokens[f.ordinalToken] = buildOrdinal(get, f); 3593 } 3594 if (f.ldmlPaddedToken) { 3595 ldmlTokens[f.ldmlPaddedToken] = buildPadded(get, f.ldmlPaddedToken.length); 3596 } 3597 if (f.ldmlTwoDigitToken) { 3598 ldmlTokens[f.ldmlTwoDigitToken] = buildPadded(buildTwoDigits(get), 2); 3599 } 3600 if (f.strfTwoDigitToken) { 3601 strfTokens[f.strfTwoDigitToken] = buildPadded(buildTwoDigits(get), 2); 3602 } 3603 if (f.strfPadding) { 3604 getPadded = buildPadded(get, f.strfPadding); 3605 } 3606 if (f.alias) { 3607 get = buildAlias(f.alias); 3608 } 3609 if (f.allowAlternates) { 3610 buildAlternates(f); 3611 } 3612 addFormats(ldmlTokens, f.ldml, get); 3613 addFormats(strfTokens, f.strf, getPadded || get); 3614 }); 3615 3616 forEachProperty(CoreOutputFormats, function(src, name) { 3617 addFormats(ldmlTokens, name, buildAlias(src)); 3618 }); 3619 3620 defineInstanceSimilar(sugarDate, 'short medium long full', function(methods, name) { 3621 var fn = getIdentityFormat(name); 3622 addFormats(ldmlTokens, name, fn); 3623 methods[name] = fn; 3624 }); 3625 3626 addFormats(ldmlTokens, 'time', getIdentityFormat('time')); 3627 addFormats(ldmlTokens, 'stamp', getIdentityFormat('stamp')); 3628 } 3629 3630 // Format matcher 3631 3632 var dateFormatMatcher; 3633 3634 function buildDateFormatMatcher() { 3635 3636 function getLdml(d, token, localeCode) { 3637 return getOwn(ldmlTokens, token)(d, localeCode); 3638 } 3639 3640 function getStrf(d, token, localeCode) { 3641 return getOwn(strfTokens, token)(d, localeCode); 3642 } 3643 3644 function checkDateToken(ldml, strf) { 3645 return hasOwn(ldmlTokens, ldml) || hasOwn(strfTokens, strf); 3646 } 3647 3648 // Format matcher for LDML or STRF tokens. 3649 dateFormatMatcher = createFormatMatcher(getLdml, getStrf, checkDateToken); 3650 } 3651 3652 // Comparison helpers 3653 3654 function fullCompareDate(date, d, margin) { 3655 var tmp; 3656 if (!dateIsValid(date)) return; 3657 if (isString(d)) { 3658 d = trim(d).toLowerCase(); 3659 switch (true) { 3660 case d === 'future': return date.getTime() > getNewDate().getTime(); 3661 case d === 'past': return date.getTime() < getNewDate().getTime(); 3662 case d === 'today': return compareDay(date); 3663 case d === 'tomorrow': return compareDay(date, 1); 3664 case d === 'yesterday': return compareDay(date, -1); 3665 case d === 'weekday': return getWeekday(date) > 0 && getWeekday(date) < 6; 3666 case d === 'weekend': return getWeekday(date) === 0 || getWeekday(date) === 6; 3667 3668 case (isDefined(tmp = English.weekdayMap[d])): 3669 return getWeekday(date) === tmp; 3670 case (isDefined(tmp = English.monthMap[d])): 3671 return getMonth(date) === tmp; 3672 } 3673 } 3674 return compareDate(date, d, margin); 3675 } 3676 3677 function compareDate(date, d, margin, localeCode, options) { 3678 var loMargin = 0, hiMargin = 0, timezoneShift, compareEdges, override, min, max, p, t; 3679 3680 function getTimezoneShift() { 3681 // If there is any specificity in the date then we're implicitly not 3682 // checking absolute time, so ignore timezone shifts. 3683 if (p.set && p.set.specificity) { 3684 return 0; 3685 } 3686 return (tzOffset(p.date) - tzOffset(date)) * MINUTES; 3687 } 3688 3689 function addSpecificUnit() { 3690 var unit = DateUnits[p.set.specificity]; 3691 return advanceDate(cloneDate(p.date), unit.name, 1).getTime() - 1; 3692 } 3693 3694 if (_utc(date)) { 3695 options = options || {}; 3696 options.fromUTC = true; 3697 options.setUTC = true; 3698 } 3699 3700 p = getExtendedDate(null, d, options, true); 3701 3702 if (margin > 0) { 3703 loMargin = hiMargin = margin; 3704 override = true; 3705 } 3706 if (!dateIsValid(p.date)) return false; 3707 if (p.set && p.set.specificity) { 3708 if (isDefined(p.set.edge) || isDefined(p.set.shift)) { 3709 compareEdges = true; 3710 moveToBeginningOfUnit(p.date, p.set.specificity, localeCode); 3711 } 3712 if (compareEdges || p.set.specificity === MONTH_INDEX) { 3713 max = moveToEndOfUnit(cloneDate(p.date), p.set.specificity, localeCode).getTime(); 3714 } else { 3715 max = addSpecificUnit(); 3716 } 3717 if (!override && isDefined(p.set.sign) && p.set.specificity) { 3718 // If the time is relative, there can occasionally be an disparity between 3719 // the relative date and "now", which it is being compared to, so set an 3720 // extra margin to account for this. 3721 loMargin = 50; 3722 hiMargin = -50; 3723 } 3724 } 3725 t = date.getTime(); 3726 min = p.date.getTime(); 3727 max = max || min; 3728 timezoneShift = getTimezoneShift(); 3729 if (timezoneShift) { 3730 min -= timezoneShift; 3731 max -= timezoneShift; 3732 } 3733 return t >= (min - loMargin) && t <= (max + hiMargin); 3734 } 3735 3736 function compareDay(d, shift) { 3737 var comp = getNewDate(); 3738 if (shift) { 3739 setDate(comp, getDate(comp) + shift); 3740 } 3741 return getYear(d) === getYear(comp) && 3742 getMonth(d) === getMonth(comp) && 3743 getDate(d) === getDate(comp); 3744 } 3745 3746 // Create helpers 3747 3748 function createDate(d, options, forceClone) { 3749 return getExtendedDate(null, d, options, forceClone).date; 3750 } 3751 3752 function createDateWithContext(contextDate, d, options, forceClone) { 3753 return getExtendedDate(contextDate, d, options, forceClone).date; 3754 } 3755 3756 function getExtendedDate(contextDate, d, opt, forceClone) { 3757 3758 var date, set, loc, options, afterCallbacks, relative, weekdayDir; 3759 3760 afterCallbacks = []; 3761 options = getDateOptions(opt); 3762 3763 function getDateOptions(opt) { 3764 var options = isString(opt) ? { locale: opt} : opt || {}; 3765 options.prefer = +!!getOwn(options, 'future') - +!!getOwn(options, 'past'); 3766 return options; 3767 } 3768 3769 function getFormatParams(match, dif) { 3770 var set = getOwn(options, 'params') || {};
3771 forEach(dif.to, function(field, i) { 3772 var str = match[i + 1], token, val; 3773 if (!str) return; 3774 if (field === 'yy' || field === 'y') { 3775 field = 'year'; 3776 val = getYearFromAbbreviation(str, date, getOwn(options, 'prefer')); 3777 } else if (token = getOwn(ParsingTokens, field)) { 3778 field = token.param || field; 3779 val = getParsingTokenValue(token, str); 3780 } else { 3781 val = loc.getTokenValue(field, str); 3782 } 3783 set[field] = val; 3784 }); 3785 return set; 3786 } 3787 3788 // Clone date will set the utc flag, but it will 3789 // be overriden later, so set option flags instead. 3790 function cloneDateByFlag(d, clone) { 3791 if (_utc(d) && !isDefined(getOwn(options, 'fromUTC'))) { 3792 options.fromUTC = true; 3793 } 3794 if (_utc(d) && !isDefined(getOwn(options, 'setUTC'))) { 3795 options.setUTC = true; 3796 } 3797 if (clone) { 3798 d = new Date(d.getTime()); 3799 } 3800 return d; 3801 } 3802 3803 function afterDateSet(fn) { 3804 afterCallbacks.push(fn); 3805 } 3806 3807 function fireCallbacks() { 3808 forEach(afterCallbacks, function(fn) { 3809 fn.call(); 3810 }); 3811 } 3812 3813 function parseStringDate(str) { 3814 3815 str = str.toLowerCase(); 3816 3817 // The act of getting the locale will initialize 3818 // if it is missing and add the required formats. 3819 loc = localeManager.get(getOwn(options, 'locale')); 3820 3821 for (var i = 0, dif, match; dif = loc.compiledFormats[i]; i++) { 3822 match = str.match(dif.reg); 3823 if (match) { 3824 3825 // Note that caching the format will modify the compiledFormats array 3826 // which is not a good idea to do inside its for loop, however we 3827 // know at this point that we have a matched format and that we will 3828 // break out below, so simpler to do it here. 3829 loc.cacheFormat(dif, i); 3830 3831 set = getFormatParams(match, dif); 3832 3833 if (isDefined(set.timestamp)) { 3834 str = set.timestamp; 3835 set = null; 3836 break; 3837 } 3838 3839 if (isDefined(set.ampm)) { 3840 handleAmpm(set.ampm); 3841 } 3842 3843 if (set.utc || isDefined(set.tzHour)) { 3844 handleTimezoneOffset(set.tzHour, set.tzMinute, set.tzSign); 3845 } 3846 3847 if (isDefined(set.shift) && isUndefined(set.unit)) { 3848 // "next january", "next monday", etc 3849 handleUnitlessShift(); 3850 } 3851 3852 if (isDefined(set.num) && isUndefined(set.unit)) { 3853 // "the second of January", etc 3854 handleUnitlessNum(set.num); 3855 } 3856 3857 if (set.midday) { 3858 // "noon" and "midnight" 3859 handleMidday(set.midday); 3860 } 3861 3862 if (isDefined(set.day)) { 3863 // Relative day localizations such as "today" and "tomorrow". 3864 handleRelativeDay(set.day); 3865 } 3866 3867 if (isDefined(set.unit)) { 3868 // "3 days ago", etc 3869 handleRelativeUnit(set.unit); 3870 } 3871 3872 if (set.edge) { 3873 // "the end of January", etc 3874 handleEdge(set.edge, set); 3875 } 3876 3877 if (set.yearSign) { 3878 set.year *= set.yearSign; 3879 } 3880 3881 break; 3882 } 3883 } 3884 3885 if (!set) { 3886 // Fall back to native parsing 3887 date = new Date(str); 3888 if (getOwn(options, 'fromUTC')) { 3889 // Falling back to system date here which cannot be parsed as UTC, 3890 // so if we're forcing UTC then simply add the offset. 3891 date.setTime(date.getTime() + (tzOffset(date) * MINUTES)); 3892 } 3893 } else if (relative) { 3894 updateDate(date, set, false, 1); 3895 } else { 3896 if (_utc(date)) { 3897 // UTC times can traverse into other days or even months, 3898 // so preemtively reset the time here to prevent this. 3899 resetTime(date); 3900 } 3901 updateDate(date, set, true, 0, getOwn(options, 'prefer'), weekdayDir); 3902 } 3903 fireCallbacks(); 3904 return date; 3905 } 3906 3907 function handleAmpm(ampm) { 3908 if (ampm === 1 && set.hour < 12) {
3909 // If the time is 1pm-11pm advance the time by 12 hours. 3910 set.hour += 12; 3911 } else if (ampm === 0 && set.hour === 12) { 3912 // If it is 12:00am then set the hour to 0. 3913 set.hour = 0; 3914 } 3915 } 3916 3917 function handleTimezoneOffset(tzHour, tzMinute, tzSign) { 3918 // Adjust for timezone offset 3919 _utc(date, true); 3920 var offset = (tzSign || 1) * ((tzHour || 0) * 60 + (tzMinute || 0)); 3921 if (offset) { 3922 set.minute = (set.minute || 0) - offset; 3923 } 3924 } 3925 3926 function handleUnitlessShift() { 3927 if (isDefined(set.month)) { 3928 // "next January" 3929 set.unit = YEAR_INDEX; 3930 } else if (isDefined(set.weekday)) { 3931 // "next Monday" 3932 set.unit = WEEK_INDEX; 3933 } 3934 } 3935 3936 function handleUnitlessNum(num) { 3937 if (isDefined(set.weekday)) { 3938 // "The second Tuesday of March" 3939 setOrdinalWeekday(num); 3940 } else if (isDefined(set.month)) { 3941 // "The second of March" 3942 set.date = set.num; 3943 } 3944 } 3945 3946 function handleMidday(hour) { 3947 set.hour = hour % 24; 3948 if (hour > 23) { 3949 // If the date has hours past 24, we need to prevent it from traversing 3950 // into a new day as that would make it being part of a new week in 3951 // ambiguous dates such as "Monday". 3952 afterDateSet(function() { 3953 advanceDate(date, 'date', trunc(hour / 24)); 3954 }); 3955 } 3956 } 3957 3958 function handleRelativeDay() { 3959 resetTime(date); 3960 if (isUndefined(set.unit)) { 3961 set.unit = DAY_INDEX; 3962 set.num = set.day; 3963 delete set.day; 3964 } 3965 } 3966 3967 function handleRelativeUnit(unitIndex) { 3968 var num = isDefined(set.num) ? set.num : 1; 3969 3970 // If a weekday is defined, there are 3 possible formats being applied: 3971 // 3972 // 1. "the day after monday": unit is days 3973 // 2. "next monday": short for "next week monday", unit is weeks 3974 // 3. "the 2nd monday of next month": unit is months 3975 // 3976 // In the first case, we need to set the weekday up front, as the day is 3977 // relative to it. The second case also needs to be handled up front for 3978 // formats like "next monday at midnight" which will have its weekday reset 3979 // if not set up front. The last case will set up the params necessary to 3980 // shift the weekday and allow separateAbsoluteUnits below to handle setting 3981 // it after the date has been shifted. 3982 if (isDefined(set.weekday)) { 3983 if (unitIndex === MONTH_INDEX) { 3984 setOrdinalWeekday(num); 3985 num = 1; 3986 } else { 3987 updateDate(date, { weekday: set.weekday }, true); 3988 delete set.weekday; 3989 } 3990 } 3991 3992 if (set.half) { 3993 // Allow localized "half" as a standalone colloquialism. Purposely avoiding 3994 // the locale number system to reduce complexity. The units "month" and 3995 // "week" are purposely excluded in the English date formats below, as 3996 // "half a week" and "half a month" are meaningless as exact dates. 3997 num *= set.half; 3998 } 3999 4000 if (isDefined(set.shift)) { 4001 // Shift and unit, ie "next month", "last week", etc. 4002 num *= set.shift; 4003 } else if (set.sign) { 4004 // Unit and sign, ie "months ago", "weeks from now", etc. 4005 num *= set.sign; 4006 } 4007 4008 if (isDefined(set.day)) { 4009 // "the day after tomorrow" 4010 num += set.day; 4011 delete set.day; 4012 } 4013 4014 // Formats like "the 15th of last month" or "6:30pm of next week" 4015 // contain absolute units in addition to relative ones, so separate 4016 // them here, remove them from the params, and set up a callback to 4017 // set them after the relative ones have been set. 4018 separateAbsoluteUnits(unitIndex); 4019 4020 // Finally shift the unit. 4021 set[English.units[unitIndex]] = num; 4022 relative = true; 4023 } 4024 4025 function handleEdge(edge, params) { 4026 var edgeIndex = params.unit, weekdayOfMonth; 4027 if (!edgeIndex) { 4028 // If we have "the end of January", then we need to find the unit index. 4029 iterateOverHigherDateParams(params, function(unitName, val, unit, i) { 4030 if (unitName === 'weekday' && isDefined(params.month)) { 4031 // If both a month and weekday exist, then we have a format like 4032 // "the last tuesday in November, 2012", where the "last" is still 4033 // relative to the end of the month, so prevent the unit "weekday" 4034 // from taking over. 4035 return; 4036 } 4037 edgeIndex = i; 4038 }); 4039 } 4040 if (edgeIndex === MONTH_INDEX && isDefined(params.weekday)) { 4041 // If a weekday in a month exists (as described above), 4042 // then set it up to be set after the date has been shifted. 4043 weekdayOfMonth = params.weekday; 4044 delete params.weekday; 4045 } 4046 afterDateSet(function() { 4047 var stopIndex; 4048 // "edge" values that are at the very edge are "2" so the beginning of the 4049 // year is -2 and the end of the year is 2. Conversely, the "last day" is 4050 // actually 00:00am so it is 1. -1 is reserved but unused for now. 4051 if (edge < 0) { 4052 moveToBeginningOfUnit(date, edgeIndex, getOwn(options, 'locale')); 4053 } else if (edge > 0) { 4054 if (edge === 1) { 4055 stopIndex = DAY_INDEX; 4056 moveToBeginningOfUnit(date, DAY_INDEX); 4057 } 4058 moveToEndOfUnit(date, edgeIndex, getOwn(options, 'locale'), stopIndex); 4059 } 4060 if (isDefined(weekdayOfMonth)) { 4061 setWeekday(date, weekdayOfMonth, -edge); 4062 resetTime(date); 4063 } 4064 }); 4065 if (edgeIndex === MONTH_INDEX) { 4066 params.specificity = DAY_INDEX; 4067 } else { 4068 params.specificity = edgeIndex - 1; 4069 } 4070 } 4071 4072 function setOrdinalWeekday(num) { 4073 // If we have "the 2nd Tuesday of June", then pass the "weekdayDir" 4074 // flag along to updateDate so that the date does not accidentally traverse 4075 // into the previous month. This needs to be independent of the "prefer" 4076 // flag because we are only ensuring that the weekday is in the future, not 4077 // the entire date. 4078 set.weekday = 7 * (num - 1) + set.weekday; 4079 set.date = 1; 4080 weekdayDir = 1; 4081 } 4082 4083 function separateAbsoluteUnits(unitIndex) { 4084 var params; 4085 4086 iterateOverDateParams(set, function(name, val, unit, i) { 4087 // If there is a time unit set that is more specific than 4088 // the matched unit we have a string like "5:30am in 2 minutes", 4089 // which is meaningless, so invalidate the date... 4090 if (i >= unitIndex) { 4091 date.setTime(NaN); 4092 return false; 4093 } else if (i < unitIndex) { 4094 // ...otherwise set the params to set the absolute date 4095 // as a callback after the relative date has been set. 4096 params = params || {}; 4097 params[name] = val; 4098 deleteDateParam(set, name); 4099 } 4100 }); 4101 if (params) { 4102 afterDateSet(function() { 4103 updateDate(date, params, true, false, getOwn(options, 'prefer'), weekdayDir); 4104 }); 4105 if (set.edge) { 4106 // "the end of March of next year" 4107 handleEdge(set.edge, params); 4108 delete set.edge; 4109 } 4110 } 4111 } 4112 4113 if (contextDate && d) { 4114 // If a context date is passed ("get" and "unitsFromNow"), 4115 // then use it as the starting point. 4116 date = cloneDateByFlag(contextDate, true); 4117 } else { 4118 date = getNewDate(); 4119 } 4120 4121 _utc(date, getOwn(options, 'fromUTC')); 4122 4123 if (isString(d)) { 4124 date = parseStringDate(d); 4125 } else if (isDate(d)) { 4126 date = cloneDateByFlag(d, hasOwn(options, 'clone') || forceClone); 4127 } else if (isObjectType(d)) { 4128 set = simpleClone(d); 4129 updateDate(date, set, true); 4130 } else if (isNumber(d) || d === null) { 4131 date.setTime(d); 4132 } 4133 // A date created by parsing a string presumes that the format *itself* is 4134 // UTC, but not that the date, once created, should be manipulated as such. In 4135 // other words, if you are creating a date object from a server time 4136 // "2012-11-15T12:00:00Z", in the majority of cases you are using it to create 4137 // a date that will, after creation, be manipulated as local, so reset the utc 4138 // flag here unless "setUTC" is also set. 4139 _utc(date, !!getOwn(options, 'setUTC')); 4140 return { 4141 set: set, 4142 date: date 4143 }; 4144 } 4145 4146 function updateDate(d, params, reset, advance, prefer, weekdayDir) { 4147 var upperUnitIndex; 4148 4149 function setUpperUnit(unitName, unitIndex) { 4150 if (prefer && !upperUnitIndex) { 4151 if (unitName === 'weekday') { 4152 upperUnitIndex = WEEK_INDEX; 4153 } else { 4154 upperUnitIndex = getHigherUnitIndex(unitIndex); 4155 } 4156 } 4157 } 4158 4159 function setSpecificity(unitIndex) { 4160 // Other functions may preemptively set the specificity before arriving 4161 // here so concede to them if they have already set more specific units. 4162 if (unitIndex > params.specificity) { 4163 return; 4164 } 4165 params.specificity = unitIndex; 4166 } 4167 4168 function canDisambiguate() { 4169 if (!upperUnitIndex || upperUnitIndex > YEAR_INDEX) { 4170 return; 4171 } 4172 switch (prefer) { 4173 case -1: return d > getNewDate(); 4174 case 1: return d < getNewDate(); 4175 } 4176 } 4177
4178 function disambiguateHigherUnit() { 4179 var unit = DateUnits[upperUnitIndex]; 4180 advance = prefer; 4181 setUnit(unit.name, 1, unit, upperUnitIndex); 4182 } 4183 4184 function handleFraction(unit, unitIndex, fraction) { 4185 if (unitIndex) { 4186 var lowerUnit = DateUnits[getLowerUnitIndex(unitIndex)]; 4187 var val = round(unit.multiplier / lowerUnit.multiplier * fraction); 4188 params[lowerUnit.name] = val; 4189 } 4190 } 4191 4192 function monthHasShifted(d, targetMonth) { 4193 if (targetMonth < 0) { 4194 targetMonth = targetMonth % 12 + 12; 4195 } 4196 return targetMonth % 12 !== getMonth(d); 4197 } 4198 4199 function setUnit(unitName, value, unit, unitIndex) { 4200 var method = unit.method, checkMonth, fraction; 4201 4202 setUpperUnit(unitName, unitIndex); 4203 setSpecificity(unitIndex); 4204 4205 fraction = value % 1; 4206 if (fraction) { 4207 handleFraction(unit, unitIndex, fraction); 4208 value = trunc(value); 4209 } 4210 4211 if (unitName === 'weekday') { 4212 if (!advance) { 4213 // Weekdays are always considered absolute units so simply set them 4214 // here even if it is an "advance" operation. This is to help avoid 4215 // ambiguous meanings in "advance" as well as to neatly allow formats 4216 // like "Wednesday of next week" without more complex logic. 4217 setWeekday(d, value, weekdayDir); 4218 } 4219 return; 4220 } 4221 checkMonth = unitIndex === MONTH_INDEX && getDate(d) > 28; 4222 4223 // If we are advancing or rewinding, then we need we need to set the 4224 // absolute time if the unit is "hours" or less. This is due to the fact 4225 // that setting by method is ambiguous during DST shifts. For example, 4226 // 1:00am on November 1st 2015 occurs twice in North American timezones 4227 // with DST, the second time being after the clocks are rolled back at 4228 // 2:00am. When springing forward this is automatically handled as there 4229 // is no 2:00am so the date automatically jumps to 3:00am. However, when 4230 // rolling back, setHours(2) will always choose the first "2am" even if 4231 // the date is currently set to the second, causing unintended jumps. 4232 // This ambiguity is unavoidable when setting dates as the notation is 4233 // ambiguous. However when advancing, we clearly want the resulting date 4234 // to be an acutal hour ahead, which can only be accomplished by setting 4235 // the absolute time. Conversely, any unit higher than "hours" MUST use 4236 // the internal set methods, as they are ambiguous as absolute units of 4237 // time. Years may be 365 or 366 days depending on leap years, months are 4238 // all over the place, and even days may be 23-25 hours depending on DST 4239 // shifts. Finally, note that the kind of jumping described above will 4240 // occur when calling ANY "set" method on the date and will occur even if 4241 // the value being set is identical to the one currently set (i.e. 4242 // setHours(2) on a date at 2am may not be a noop). This is precarious, 4243 // so avoiding this situation in callDateSet by checking up front that 4244 // the value is not the same before setting. 4245 if (advance && !unit.ambiguous) { 4246 d.setTime(d.getTime() + (value * advance * unit.multiplier)); 4247 return; 4248 } else if (advance) { 4249 if (unitIndex === WEEK_INDEX) { 4250 value *= 7; 4251 method = DateUnits[DAY_INDEX].method; 4252 } 4253 value = (value * advance) + callDateGet(d, method); 4254 } 4255 callDateSetWithWeek(d, method, value, advance); 4256 if (checkMonth && monthHasShifted(d, value)) { 4257 // As we are setting the units in reverse order, there is a chance that
4258 // our date may accidentally traverse into a new month, such as setting 4259 // { month: 1, date 15 } on January 31st. Check for this here and reset 4260 // the date to the last day of the previous month if this has happened. 4261 setDate(d, 0); 4262 } 4263 } 4264 4265 if (isNumber(params) && advance) { 4266 // If param is a number and advancing, the number is in milliseconds. 4267 params = { millisecond: params }; 4268 } else if (isNumber(params)) { 4269 // Otherwise just set the timestamp and return. 4270 d.setTime(params); 4271 return d; 4272 } 4273 4274 iterateOverDateParams(params, setUnit); 4275 4276 if (reset && params.specificity) { 4277 resetLowerUnits(d, params.specificity); 4278 } 4279 4280 // If past or future is preferred, then the process of "disambiguation" will 4281 // ensure that an ambiguous time/date ("4pm", "thursday", "June", etc.) will 4282 // be in the past or future. Weeks are only considered ambiguous if there is 4283 // a weekday, i.e. "thursday" is an ambiguous week, but "the 4th" is an 4284 // ambiguous month. 4285 if (canDisambiguate()) { 4286 disambiguateHigherUnit(); 4287 } 4288 return d; 4289 } 4290 4291 // Locales 4292 4293 // Locale helpers 4294 var English, localeManager; 4295 4296 function getEnglishVariant(v) { 4297 return simpleMerge(simpleClone(EnglishLocaleBaseDefinition), v); 4298 } 4299 4300 function arrayToRegAlternates(arr) { 4301 var joined = arr.join(''); 4302 if (!arr || !arr.length) { 4303 return ''; 4304 } 4305 if (joined.length === arr.length) { 4306 return '[' + joined + ']'; 4307 } 4308 // map handles sparse arrays so no need to compact the array here. 4309 return map(arr, escapeRegExp).join('|'); 4310 } 4311 4312 function getRegNonCapturing(src, opt) { 4313 if (src.length > 1) { 4314 src = '(?:' + src + ')'; 4315 } 4316 if (opt) { 4317 src += '?'; 4318 } 4319 return src; 4320 } 4321 4322 function getParsingTokenWithSuffix(field, src, suffix) { 4323 var token = LocalizedParsingTokens[field]; 4324 if (token.requiresSuffix) { 4325 src = getRegNonCapturing(src + getRegNonCapturing(suffix)); 4326 } else if (token.requiresSuffixOr) { 4327 src += getRegNonCapturing(token.requiresSuffixOr + '|' + suffix); 4328 } else { 4329 src += getRegNonCapturing(suffix, true); 4330 } 4331 return src; 4332 } 4333 4334 function getArrayWithOffset(arr, n, alternate, offset) { 4335 var val; 4336 if (alternate > 1) { 4337 val = arr[n + (alternate - 1) * offset]; 4338 } 4339 return val || arr[n]; 4340 } 4341 4342 function buildLocales() { 4343 4344 function LocaleManager(loc) { 4345 this.locales = {}; 4346 this.add(loc); 4347 } 4348 4349 LocaleManager.prototype = { 4350 4351 get: function(code, fallback) { 4352 var loc = this.locales[code]; 4353 if (!loc && LazyLoadedLocales[code]) { 4354 loc = this.add(code, LazyLoadedLocales[code]); 4355 } else if (!loc && code) { 4356 loc = this.locales[code.slice(0, 2)]; 4357 } 4358 return loc || fallback === false ? loc : this.current; 4359 }, 4360 4361 getAll: function() { 4362 return this.locales; 4363 }, 4364 4365 set: function(code) { 4366 var loc = this.get(code, false); 4367 if (!loc) { 4368 throw new TypeError('Invalid Locale: ' + code); 4369 } 4370 return this.current = loc; 4371 }, 4372 4373 add: function(code, def) { 4374 if (!def) { 4375 def = code; 4376 code = def.code; 4377 } else { 4378 def.code = code; 4379 } 4380 var loc = def.compiledFormats ? def : getNewLocale(def); 4381 this.locales[code] = loc; 4382 if (!this.current) { 4383 this.current = loc; 4384 } 4385 return loc; 4386 }, 4387 4388 remove: function(code) { 4389 if (this.current.code === code) { 4390 this.current = this.get('en'); 4391 } 4392 return delete this.locales[code]; 4393 } 4394 4395 }; 4396 4397 // Sorry about this guys... 4398 English = getNewLocale(AmericanEnglishDefinition); 4399 localeManager = new LocaleManager(English); 4400 } 4401 4402 function getNewLocale(def) { 4403 4404 function Locale(def) { 4405 this.init(def); 4406 } 4407 4408 Locale.prototype = { 4409 4410 getMonthName: function(n, alternate) { 4411 if (this.monthSuffix) { 4412 return (n + 1) + this.monthSuffix; 4413 } 4414 return getArrayWithOffset(this.months, n, alternate, 12); 4415 }, 4416 4417 getWeekdayName: function(n, alternate) { 4418 return getArrayWithOffset(this.weekdays, n, alternate, 7); 4419 }, 4420 4421 getTokenValue: function(field, str) { 4422 var map = this[field + 'Map'], val; 4423 if (map) { 4424 val = map[str]; 4425 } 4426 if (isUndefined(val)) { 4427 val = this.getNumber(str); 4428 if (field === 'month') { 4429 // Months are the only numeric date field 4430 // whose value is not the same as its number. 4431 val -= 1; 4432 } 4433 } 4434 return val; 4435 }, 4436 4437 getNumber: function(str) { 4438 var num = this.numeralMap[str]; 4439 if (isDefined(num)) { 4440 return num; 4441 } 4442 // The unary plus operator here show better performance and handles 4443 // every format that parseFloat does with the exception of trailing 4444 // characters, which are guaranteed not to be in our string at this point. 4445 num = +str.replace(/,/, '.'); 4446 if (!isNaN(num)) { 4447 return num; 4448 } 4449 num = this.getNumeralValue(str); 4450 if (!isNaN(num)) { 4451 this.numeralMap[str] = num; 4452 return num; 4453 } 4454 return num; 4455 }, 4456 4457 getNumeralValue: function(str) { 4458 var place = 1, num = 0, lastWasPlace, isPlace, numeral, digit, arr; 4459 // Note that "numerals" that need to be converted through this method are 4460 // all considered to be single characters in order to handle CJK. This 4461 // method is by no means unique to CJK, but the complexity of handling 4462 // inflections in non-CJK languages adds too much overhead for not enough 4463 // value, so avoiding for now. 4464 arr = str.split(''); 4465 for (var i = arr.length - 1; numeral = arr[i]; i--) { 4466 digit = getOwn(this.numeralMap, numeral); 4467 if (isUndefined(digit)) { 4468 digit = getOwn(fullWidthNumberMap, numeral) || 0; 4469 } 4470 isPlace = digit > 0 && digit % 10 === 0; 4471 if (isPlace) { 4472 if (lastWasPlace) { 4473 num += place; 4474 } 4475 if (i) { 4476 place = digit; 4477 } else { 4478 num += digit; 4479 } 4480 } else { 4481 num += digit * place; 4482 place *= 10; 4483 } 4484 lastWasPlace = isPlace; 4485 } 4486 return num; 4487 }, 4488 4489 getOrdinal: function(n) { 4490 var suffix = this.ordinalSuffix; 4491 return suffix || getOrdinalSuffix(n); 4492 }, 4493 4494 getRelativeFormat: function(adu, type) { 4495 return this.convertAdjustedToFormat(adu, type); 4496 }, 4497 4498 getDuration: function(ms) { 4499 return this.convertAdjustedToFormat(getAdjustedUnitForNumber(max(0, ms)), 'duration'); 4500 }, 4501 4502 getFirstDayOfWeek: function() { 4503 var val = this.firstDayOfWeek; 4504 return isDefined(val) ? val : ISO_FIRST_DAY_OF_WEEK; 4505 }, 4506 4507 getFirstDayOfWeekYear: function() { 4508 return this.firstDayOfWeekYear || ISO_FIRST_DAY_OF_WEEK_YEAR; 4509 }, 4510 4511 convertAdjustedToFormat: function(adu, type) { 4512 var sign, unit, mult, 4513 num = adu[0], 4514 u = adu[1], 4515 ms = adu[2], 4516 format = this[type] || this.relative; 4517 if (isFunction(format)) { 4518 return format.call(this, num, u, ms, type); 4519 } 4520 mult = !this.plural || num === 1 ? 0 : 1; 4521 unit = this.units[mult * 8 + u] || this.units[u]; 4522 sign = this[ms > 0 ? 'fromNow' : 'ago']; 4523 return format.replace(/\{(.*?)\}/g, function(full, match) { 4524 switch (match) { 4525 case 'num': return num; 4526 case 'unit': return unit; 4527 case 'sign': return sign; 4528 } 4529 }); 4530 }, 4531 4532 cacheFormat: function(dif, i) { 4533 this.compiledFormats.splice(i, 1); 4534 this.compiledFormats.unshift(dif); 4535 }, 4536 4537 addFormat: function(src, to) { 4538 var loc = this; 4539 4540 function getTokenSrc(str) { 4541 var suffix, src, val, 4542 opt = str.match(/\?$/), 4543 nc = str.match(/^(\d+)\??$/), 4544 slice = str.match(/(\d)(?:-(\d))?/), 4545 key = str.replace(/[^a-z]+$/i, ''); 4546 4547 // Allowing alias tokens such as {time}
4548 if (val = getOwn(loc.parsingAliases, key)) { 4549 src = replaceParsingTokens(val); 4550 if (opt) { 4551 src = getRegNonCapturing(src, true); 4552 } 4553 return src; 4554 } 4555 4556 if (nc) { 4557 src = loc.tokens[nc[1]]; 4558 } else if (val = getOwn(ParsingTokens, key)) { 4559 src = val.src; 4560 } else { 4561 val = getOwn(loc.parsingTokens, key) || getOwn(loc, key); 4562 4563 // Both the "months" array and the "month" parsing token can be accessed 4564 // by either {month} or {months}, falling back as necessary, however 4565 // regardless of whether or not a fallback occurs, the final field to 4566 // be passed to addRawFormat must be normalized as singular. 4567 key = key.replace(/s$/, ''); 4568 4569 if (!val) { 4570 val = getOwn(loc.parsingTokens, key) || getOwn(loc, key + 's'); 4571 } 4572 4573 if (isString(val)) { 4574 src = val; 4575 suffix = loc[key + 'Suffix']; 4576 } else { 4577 if (slice) { 4578 val = filter(val, function(m, i) { 4579 var mod = i % (loc.units ? 8 : val.length); 4580 return mod >= slice[1] && mod <= (slice[2] || slice[1]); 4581 }); 4582 } 4583 src = arrayToRegAlternates(val); 4584 } 4585 } 4586 if (!src) { 4587 return ''; 4588 } 4589 if (nc) { 4590 // Non-capturing tokens like {0} 4591 src = getRegNonCapturing(src); 4592 } else { 4593 // Capturing group and add to parsed tokens 4594 to.push(key); 4595 src = '(' + src + ')'; 4596 } 4597 if (suffix) { 4598 // Date/time suffixes such as those in CJK 4599 src = getParsingTokenWithSuffix(key, src, suffix); 4600 } 4601 if (opt) { 4602 src += '?'; 4603 } 4604 return src; 4605 } 4606 4607 function replaceParsingTokens(str) { 4608 4609 // Make spaces optional 4610 str = str.replace(/ /g, ' ?'); 4611 4612 return str.replace(/\{([^,]+?)\}/g, function(match, token) { 4613 var tokens = token.split('|'), src; 4614 if (tokens.length > 1) { 4615 src = getRegNonCapturing(map(tokens, getTokenSrc).join('|')); 4616 } else { 4617 src = getTokenSrc(token); 4618 } 4619 return src; 4620 }); 4621 } 4622 4623 if (!to) { 4624 to = []; 4625 src = replaceParsingTokens(src); 4626 } 4627 4628 loc.addRawFormat(src, to); 4629 }, 4630 4631 addRawFormat: function(format, to) { 4632 this.compiledFormats.unshift({ 4633 reg: RegExp('^ *' + format + ' *$', 'i'), 4634 to: to 4635 }); 4636 }, 4637 4638 init: function(def) { 4639 var loc = this; 4640 4641 // -- Initialization helpers 4642 4643 function initFormats() { 4644 loc.compiledFormats = []; 4645 loc.parsingAliases = {}; 4646 loc.parsingTokens = {}; 4647 } 4648 4649 function initDefinition() { 4650 simpleMerge(loc, def); 4651 } 4652 4653 function initArrayFields() {
4654 forEach(LOCALE_ARRAY_FIELDS, function(name) { 4655 var val = loc[name]; 4656 if (isString(val)) { 4657 loc[name] = commaSplit(val); 4658 } else if (!val) { 4659 loc[name] = []; 4660 } 4661 }); 4662 } 4663 4664 // -- Value array build helpers 4665 4666 function buildValueArray(name, mod, map, fn) { 4667 var field = name, all = [], setMap; 4668 if (!loc[field]) { 4669 field += 's'; 4670 } 4671 if (!map) { 4672 map = {}; 4673 setMap = true; 4674 } 4675 forAllAlternates(field, function(alt, j, i) { 4676 var idx = j * mod + i, val; 4677 val = fn ? fn(i) : i; 4678 map[alt] = val; 4679 map[alt.toLowerCase()] = val; 4680 all[idx] = alt; 4681 }); 4682 loc[field] = all; 4683 if (setMap) { 4684 loc[name + 'Map'] = map; 4685 } 4686 } 4687 4688 function forAllAlternates(field, fn) { 4689 forEach(loc[field], function(str, i) { 4690 forEachAlternate(str, function(alt, j) { 4691 fn(alt, j, i); 4692 }); 4693 }); 4694 } 4695 4696 function forEachAlternate(str, fn) { 4697 var arr = map(str.split('+'), function(split) { 4698 return split.replace(/(.+):(.+)$/, function(full, base, suffixes) { 4699 return map(suffixes.split('|'), function(suffix) { 4700 return base + suffix; 4701 }).join('|'); 4702 }); 4703 }).join('|'); 4704 forEach(arr.split('|'), fn); 4705 } 4706 4707 function buildNumerals() { 4708 var map = {}; 4709 buildValueArray('numeral', 10, map); 4710 buildValueArray('article', 1, map, function() { 4711 return 1; 4712 }); 4713 buildValueArray('placeholder', 4, map, function(n) { 4714 return pow(10, n + 1); 4715 }); 4716 loc.numeralMap = map; 4717 } 4718 4719 function buildTimeFormats() { 4720 loc.parsingAliases['time'] = getTimeFormat(); 4721 loc.parsingAliases['tzOffset'] = getTZOffsetFormat(); 4722 } 4723 4724 function getTimeFormat() { 4725 var src; 4726 if (loc.ampmFront) { 4727 // "ampmFront" exists mostly for CJK locales, which also presume that 4728 // time suffixes exist, allowing this to be a simpler regex. 4729 src = '{ampm?} {hour} (?:{minute} (?::?{second})?)?'; 4730 } else if (loc.ampm.length) { 4731 src = '{hour}(?:[.:]{minute}(?:[.:]{second})? {ampm?}| {ampm})'; 4732 } else { 4733 src = '{hour}(?:[.:]{minute}(?:[.:]{second})?)'; 4734 } 4735 return src; 4736 } 4737 4738 function getTZOffsetFormat() { 4739 return '(?:{Z}|{GMT?}(?:{tzSign}{tzHour}(?::?{tzMinute}(?: \\([\\w\\s]+\\))?)?)?)?'; 4740 } 4741 4742 function buildParsingTokens() { 4743 forEachProperty(LocalizedParsingTokens, function(token, name) { 4744 var src, arr; 4745 src = token.base ? ParsingTokens[token.base].src : token.src; 4746 if (token.requiresNumerals || loc.numeralUnits) { 4747 src += getNumeralSrc(); 4748 } 4749 arr = loc[name + 's']; 4750 if (arr && arr.length) { 4751 src += '|' + arrayToRegAlternates(arr); 4752 } 4753 loc.parsingTokens[name] = src; 4754 }); 4755 } 4756 4757 function getNumeralSrc() { 4758 var all, src = ''; 4759 all = loc.numerals.concat(loc.placeholders).concat(loc.articles); 4760 if (loc.allowsFullWidth) { 4761 all = all.concat(fullWidthNumbers.split('')); 4762 } 4763 if (all.length) { 4764 src = '|(?:' + arrayToRegAlternates(all) + ')+'; 4765 } 4766 return src; 4767 } 4768 4769 function buildTimeSuffixes() { 4770 iterateOverDateUnits(function(unit, i) { 4771 var token = loc.timeSuffixes[i];
4772 if (token) { 4773 loc[(unit.alias || unit.name) + 'Suffix'] = token; 4774 } 4775 }); 4776 } 4777 4778 function buildModifiers() { 4779 forEach(loc.modifiers, function(modifier) { 4780 var name = modifier.name, mapKey = name + 'Map', map; 4781 map = loc[mapKey] || {}; 4782 forEachAlternate(modifier.src, function(alt, j) { 4783 var token = getOwn(loc.parsingTokens, name), val = modifier.value; 4784 map[alt] = val; 4785 loc.parsingTokens[name] = token ? token + '|' + alt : alt; 4786 if (modifier.name === 'sign' && j === 0) { 4787 // Hooking in here to set the first "fromNow" or "ago" modifier 4788 // directly on the locale, so that it can be reused in the 4789 // relative format. 4790 loc[val === 1 ? 'fromNow' : 'ago'] = alt; 4791 } 4792 }); 4793 loc[mapKey] = map; 4794 }); 4795 } 4796 4797 // -- Format adding helpers 4798 4799 function addCoreFormats() { 4800 forEach(CoreParsingFormats, function(df) { 4801 var src = df.src; 4802 if (df.mdy && loc.mdy) { 4803 // Use the mm/dd/yyyy variant if it 4804 // exists and the locale requires it 4805 src = df.mdy; 4806 } 4807 if (df.time) { 4808 // Core formats that allow time require the time 4809 // reg on both sides, so add both versions here. 4810 loc.addFormat(getFormatWithTime(src, true)); 4811 loc.addFormat(getFormatWithTime(src)); 4812 } else { 4813 loc.addFormat(src); 4814 } 4815 }); 4816 loc.addFormat('{time}'); 4817 } 4818 4819 function addLocaleFormats() { 4820 addFormatSet('parse'); 4821 addFormatSet('timeParse', true); 4822 addFormatSet('timeFrontParse', true, true); 4823 } 4824 4825 function addFormatSet(field, allowTime, timeFront) { 4826 forEach(loc[field], function(format) { 4827 if (allowTime) { 4828 format = getFormatWithTime(format, timeFront); 4829 } 4830 loc.addFormat(format); 4831 }); 4832 } 4833 4834 function getFormatWithTime(baseFormat, timeBefore) { 4835 if (timeBefore) { 4836 return getTimeBefore() + baseFormat; 4837 } 4838 return baseFormat + getTimeAfter(); 4839 } 4840 4841 function getTimeBefore() { 4842 return getRegNonCapturing('{time}[,\\s\\u3000]', true); 4843 } 4844 4845 function getTimeAfter() { 4846 var markers = ',?[\\s\\u3000]', localized; 4847 localized = arrayToRegAlternates(loc.timeMarkers); 4848 if (localized) { 4849 markers += '| (?:' + localized + ') '; 4850 } 4851 markers = getRegNonCapturing(markers, loc.timeMarkerOptional); 4852 return getRegNonCapturing(markers + '{time}', true); 4853 } 4854 4855 initFormats(); 4856 initDefinition(); 4857 initArrayFields(); 4858 4859 buildValueArray('month', 12); 4860 buildValueArray('weekday', 7); 4861 buildValueArray('unit', 8); 4862 buildValueArray('ampm', 2); 4863 4864 buildNumerals(); 4865 buildTimeFormats(); 4866 buildParsingTokens(); 4867 buildTimeSuffixes(); 4868 buildModifiers(); 4869 4870 // The order of these formats is important. Order is reversed so formats 4871 // that are initialized later will take precedence. Generally, this means 4872 // that more specific formats should come later. 4873 addCoreFormats(); 4874 addLocaleFormats(); 4875 4876 } 4877 4878 }; 4879 4880 return new Locale(def); 4881 } 4882 4883 4884 /*** 4885 * @method [units]Since(d, [options]) 4886 * @returns Number 4887 * @short Returns the time since [d]. 4888 * @extra [d] will accept a date object, timestamp, or string. If not specified, 4889 * [d] is assumed to be now. `unitsAgo` is provided as an alias to make 4890 * this more readable when [d] is assumed to be the current date. 4891 * [options] can be an object or a locale code as a string. See `create` 4892 * for more. 4893 * 4894 * @set 4895 * millisecondsSince 4896 * secondsSince 4897 * minutesSince 4898 * hoursSince 4899 * daysSince 4900 * weeksSince 4901 * monthsSince 4902 * yearsSince 4903 * 4904 * @example 4905 * 4906 * new Date().millisecondsSince('1 hour ago') -> 3,600,000 4907 * new Date().daysSince('1 week ago') -> 7 4908 * new Date().yearsSince('15 years ago') -> 15 4909 * lastYear.yearsAgo() -> 1 4910 * 4911 * @param {string|number|Date} d 4912 * @param {DateCreateOptions} options 4913 * 4914 *** 4915 * @method [units]Ago() 4916 * @returns Number 4917 * @short Returns the time ago in the appropriate unit. 4918 * 4919 * @set 4920 * millisecondsAgo 4921 * secondsAgo 4922 * minutesAgo 4923 * hoursAgo 4924 * daysAgo 4925 * weeksAgo 4926 * monthsAgo 4927 * yearsAgo 4928 * 4929 * @example 4930 * 4931 * lastYear.millisecondsAgo() -> 3,600,000 4932 * lastYear.daysAgo() -> 7 4933 * lastYear.yearsAgo() -> 15 4934 * 4935 *** 4936 * @method [units]Until([d], [options]) 4937 * @returns Number 4938 * @short Returns the time until [d]. 4939 * @extra [d] will accept a date object, timestamp, or string. If not specified, 4940 * [d] is assumed to be now. `unitsFromNow` is provided as an alias to 4941 * make this more readable when [d] is assumed to be the current date. 4942 * [options] can be an object or a locale code as a string. See `create` 4943 * for more. 4944 * 4945 * 4946 * @set 4947 * millisecondsUntil 4948 * secondsUntil 4949 * minutesUntil 4950 * hoursUntil 4951 * daysUntil
4952 * weeksUntil 4953 * monthsUntil 4954 * yearsUntil 4955 * 4956 * @example 4957 * 4958 * new Date().millisecondsUntil('1 hour from now') -> 3,600,000 4959 * new Date().daysUntil('1 week from now') -> 7 4960 * new Date().yearsUntil('15 years from now') -> 15 4961 * nextYear.yearsFromNow() -> 1 4962 * 4963 * @param {string|number|Date} d 4964 * @param {DateCreateOptions} options 4965 * 4966 *** 4967 * @method [units]FromNow() 4968 * @returns Number 4969 * @short Returns the time from now in the appropriate unit. 4970 * 4971 * @set 4972 * millisecondsFromNow 4973 * secondsFromNow 4974 * minutesFromNow 4975 * hoursFromNow 4976 * daysFromNow 4977 * weeksFromNow 4978 * monthsFromNow 4979 * yearsFromNow 4980 * 4981 * @example 4982 * 4983 * nextYear.millisecondsFromNow() -> 3,600,000 4984 * nextYear.daysFromNow() -> 7 4985 * nextYear.yearsFromNow() -> 15 4986 * 4987 *** 4988 * @method add[Units](n, [reset] = false) 4989 * @returns Date 4990 * @short Adds `n` units to the date. If [reset] is true, all lower units will 4991 * be reset. 4992 * @extra This method modifies the date! Note that in the case of `addMonths`, 4993 * the date may fall on a date that doesn't exist (i.e. February 30). In 4994 * this case the date will be shifted to the last day of the month. Don't 4995 * use `addMonths` if you need precision. 4996 * 4997 * @set 4998 * addMilliseconds 4999 * addSeconds 5000 * addMinutes 5001 * addHours 5002 * addDays 5003 * addWeeks 5004 * addMonths 5005 * addYears 5006 * 5007 * @example 5008 * 5009 * new Date().addYears(5) -> current time + 5 years 5010 * new Date().addDays(5) -> current time + 5 days 5011 * new Date().addDays(5, true) -> current time + 5 days (time reset) 5012 * 5013 * @param {number} n 5014 * @param {boolean} [reset] 5015 * 5016 *** 5017 * @method isLast[Unit]([localeCode]) 5018 * @returns Boolean 5019 * @short Returns true if the date is last week, month, or year. 5020 * @extra This method takes an optional locale code for `isLastWeek`, which is 5021 * locale dependent. The default locale code is `en`, which places 5022 * Sunday at the beginning of the week. You can pass `en-GB` as a quick 5023 * way to force Monday as the beginning of the week. 5024 * 5025 * @set 5026 * isLastWeek 5027 * isLastMonth 5028 * isLastYear 5029 * 5030 * @example 5031 * 5032 * yesterday.isLastWeek() -> true or false? 5033 * yesterday.isLastMonth() -> probably not... 5034 * yesterday.isLastYear() -> even less likely... 5035 * 5036 * @param {string} [localeCode] 5037 * 5038 *** 5039 * @method isThis[Unit]([localeCode]) 5040 * @returns Boolean 5041 * @short Returns true if the date is this week, month, or year. 5042 * @extra This method takes an optional locale code for `isThisWeek`, which is 5043 * locale dependent. The default locale code is `en`, which places 5044 * Sunday at the beginning of the week. You can pass `en-GB` as a quick 5045 * way to force Monday as the beginning of the week. 5046 * 5047 * @set 5048 * isThisWeek 5049 * isThisMonth 5050 * isThisYear 5051 * 5052 * @example 5053 * 5054 * tomorrow.isThisWeek() -> true or false? 5055 * tomorrow.isThisMonth() -> probably... 5056 * tomorrow.isThisYear() -> signs point to yes... 5057 * 5058 * @param {string} [localeCode] 5059 * 5060 *** 5061 * @method isNext[Unit]([localeCode]) 5062 * @returns Boolean 5063 * @short Returns true if the date is next week, month, or year. 5064 * @extra This method takes an optional locale code for `isNextWeek`, which is 5065 * locale dependent. The default locale code is `en`, which places 5066 * Sunday at the beginning of the week. You can pass `en-GB` as a quick 5067 * way to force Monday as the beginning of the week. 5068 * 5069 * @set 5070 * isNextWeek 5071 * isNextMonth 5072 * isNextYear 5073 * 5074 * @example 5075 * 5076 * tomorrow.isNextWeek() -> true or false? 5077 * tomorrow.isNextMonth() -> probably not... 5078 * tomorrow.isNextYear() -> even less likely... 5079 * 5080 * @param {string} [localeCode] 5081 * 5082 *** 5083 * @method beginningOf[Unit]([localeCode]) 5084 * @returns Date 5085 * @short Sets the date to the beginning of the appropriate unit. 5086 * @extra This method modifies the date! A locale code can be passed for 5087 * `beginningOfWeek`, which is locale dependent. If consistency is 5088 * needed, use `beginningOfISOWeek` instead. 5089 * 5090 * @set 5091 * beginningOfDay 5092 * beginningOfWeek 5093 * beginningOfMonth 5094 * beginningOfYear 5095 * 5096 * @example 5097 * 5098 * new Date().beginningOfDay() -> the beginning of today (resets the time) 5099 * new Date().beginningOfWeek() -> the beginning of the week 5100 * new Date().beginningOfMonth() -> the beginning of the month 5101 * new Date().beginningOfYear() -> the beginning of the year 5102 * 5103 * @param {string} [localeCode] 5104 * 5105 *** 5106 * @method endOf[Unit]([localeCode]) 5107 * @returns Date 5108 * @short Sets the date to the end of the appropriate unit. 5109 * @extra This method modifies the date! A locale code can be passed for 5110 * `endOfWeek`, which is locale dependent. If consistency is needed, use
5111 * `endOfISOWeek` instead. 5112 * 5113 * @set 5114 * endOfDay 5115 * endOfWeek 5116 * endOfMonth 5117 * endOfYear 5118 * 5119 * @example 5120 * 5121 * new Date().endOfDay() -> the end of today (sets the time to 23:59:59.999) 5122 * new Date().endOfWeek() -> the end of the week 5123 * new Date().endOfMonth() -> the end of the month 5124 * new Date().endOfYear() -> the end of the year 5125 * 5126 * @param {string} [localeCode] 5127 * 5128 ***/ 5129 function buildDateUnitMethods() { 5130 5131 defineInstanceSimilar(sugarDate, DateUnits, function(methods, unit, index) { 5132 var name = unit.name, caps = simpleCapitalize(name); 5133 5134 if (index > DAY_INDEX) { 5135 forEach(['Last', 'This', 'Next'], function(shift) { 5136 methods['is' + shift + caps] = function(d, localeCode) { 5137 return compareDate(d, shift + ' ' + name, 0, localeCode, { locale: 'en' }); 5138 }; 5139 }); 5140 } 5141 if (index > HOURS_INDEX) { 5142 methods['beginningOf' + caps] = function(d, localeCode) { 5143 return moveToBeginningOfUnit(d, index, localeCode); 5144 }; 5145 methods['endOf' + caps] = function(d, localeCode) { 5146 return moveToEndOfUnit(d, index, localeCode); 5147 }; 5148 } 5149 5150 methods['add' + caps + 's'] = function(d, num, reset) { 5151 return advanceDate(d, name, num, reset); 5152 }; 5153 5154 var since = function(date, d, options) { 5155 return getTimeDistanceForUnit(date, createDateWithContext(date, d, options, true), unit); 5156 }; 5157 var until = function(date, d, options) { 5158 return getTimeDistanceForUnit(createDateWithContext(date, d, options, true), date, unit); 5159 }; 5160 5161 methods[name + 'sAgo'] = methods[name + 'sUntil'] = until; 5162 methods[name + 'sSince'] = methods[name + 'sFromNow'] = since; 5163 5164 }); 5165 5166 } 5167 5168 /*** 5169 * @method is[Day]() 5170 * @returns Boolean 5171 * @short Returns true if the date falls on the specified day. 5172 * 5173 * @set 5174 * isToday 5175 * isYesterday 5176 * isTomorrow 5177 * isWeekday 5178 * isWeekend 5179 * isSunday 5180 * isMonday 5181 * isTuesday 5182 * isWednesday 5183 * isThursday 5184 * isFriday 5185 * isSaturday 5186 * 5187 * @example 5188 * 5189 * tomorrow.isToday() -> false 5190 * thursday.isTomorrow() -> ? 5191 * yesterday.isWednesday() -> ? 5192 * today.isWeekend() -> ? 5193 * 5194 *** 5195 * @method isFuture() 5196 * @returns Boolean 5197 * @short Returns true if the date is in the future. 5198 * 5199 * @example 5200 * 5201 * lastWeek.isFuture() -> false 5202 * nextWeek.isFuture() -> true 5203 * 5204 *** 5205 * @method isPast() 5206 * @returns Boolean 5207 * @short Returns true if the date is in the past. 5208 * 5209 * @example 5210 * 5211 * lastWeek.isPast() -> true 5212 * nextWeek.isPast() -> false 5213 * 5214 ***/ 5215 function buildRelativeAliases() { 5216 var special = spaceSplit('Today Yesterday Tomorrow Weekday Weekend Future Past'); 5217 var weekdays = English.weekdays.slice(0, 7); 5218 var months = English.months.slice(0, 12); 5219 var together = special.concat(weekdays).concat(months); 5220 defineInstanceSimilar(sugarDate, together, function(methods, name) { 5221 methods['is' + name] = function(d) { 5222 return fullCompareDate(d, name); 5223 }; 5224 }); 5225 } 5226 5227 defineStatic(sugarDate, { 5228 5229 /*** 5230 * @method create(d, [options]) 5231 * @returns Date 5232 * @static 5233 * @short Alternate date constructor which accepts text formats, a timestamp, 5234 * objects, or another date. 5235 * @extra If no argument is given, the date is assumed to be now. The second 5236 * argument can either be an options object or a locale code as a 5237 * shortcut. For more, see `date parsing`. 5238 * 5239 * @options 5240 * 5241 * locale A locale code to parse the date in. This can also be passed as 5242 * the second argument to this method. Default is the current 5243 * locale, which is `en` if none is set. 5244 * 5245 * past If `true`, ambiguous dates like `Sunday` will be parsed as
5246 * `last Sunday`. Note that non-ambiguous dates are not 5247 * guaranteed to be in the past. 5248 * Default is `false`. 5249 * 5250 * future If `true`, ambiguous dates like `Sunday` will be parsed as 5251 * `next Sunday`. Note that non-ambiguous dates are not 5252 * guaranteed to be in the future. 5253 * Default is `false`. 5254 * 5255 * fromUTC If `true`, dates with no timezone notation will be parsed as 5256 * UTC (no timezone offset). This is useful for server 5257 * timestamps, etc. Note that this flag is not required if the 5258 * timezone is specified in the string, either as an explicit 5259 * value (ex. +0900 or -09:00) or "Z", which is UTC time. 5260 * 5261 * setUTC If `true`, this will set a flag on the date that tells Sugar 5262 * to internally use UTC methods like `getUTCHours` when handling 5263 * it. This flag is the same as calling the `setUTC` method on 5264 * the date after parsing is complete. Note that this is 5265 * different from `fromUTC`, which parses a string as UTC, but 5266 * does not set this flag. 5267 * 5268 * clone If `true` and `d` is a date, it will be cloned. 5269 * 5270 * params An optional object that is populated with properties that are 5271 * parsed from string input. This option is useful when parsed 5272 * properties need to be retained. 5273 * 5274 * @example 5275 * 5276 * Date.create('July') -> July of this year 5277 * Date.create('1776') -> 1776 5278 * Date.create('today') -> today 5279 * Date.create('Wednesday') -> This wednesday 5280 * Date.create('next Friday') -> Next friday 5281 * Date.create('July 4, 1776') -> July 4, 1776 5282 * Date.create(-446806800000) -> November 5, 1955 5283 * Date.create('1776å¹´07æ04æ¥', 'ja') -> July 4, 1776 5284 * Date.create('August', {past: true}) -> August of this or last year 5285 * Date.create('August', {future: true}) -> August of this or next year 5286 * Date.create('Thursday', {fromUTC: true}) -> Thursday at 12:00am UTC time 5287 * 5288 * @param {string|number|Date} d 5289 * @param {DateCreateOptions} [options] 5290 * 5291 * @option {string} [locale] 5292 * @option {boolean} [past] 5293 * @option {boolean} [future] 5294 * @option {boolean} [fromUTC] 5295 * @option {boolean} [setUTC] 5296 * @option {boolean} [clone] 5297 * @option {Object} [params] 5298 * 5299 ***/ 5300 'create': function(d, options) { 5301 return createDate(d, options); 5302 }, 5303 5304 /*** 5305 * @method getLocale([localeCode] = current) 5306 * @returns Locale 5307 * @static 5308 * @short Gets the locale object for the given code, or the current locale. 5309 * @extra The locale object has various properties that dictate how dates are 5310 * parsed and formatted for that locale. The locale object is exposed 5311 * here mostly for introspection - it should be uncommon to need to 5312 * maniplate the object itself. For more, see `date locales`. 5313 * 5314 * @example 5315 * 5316 * Date.getLocale() -> Returns the current locale 5317 * Date.getLocale('en') -> Returns the EN locale 5318 * 5319 * @param {string} [localeCode] 5320 * 5321 ***/ 5322 'getLocale': function(code) { 5323 return localeManager.get(code, !code); 5324 }, 5325 5326 /*** 5327 * @method getAllLocales() 5328 * @returns Array<Locale> 5329 * @static 5330 * @short Returns all available locales as an object. 5331 * @extra For more, see `date locales`. 5332 * @example 5333 * 5334 * Date.getAllLocales() 5335 * 5336 ***/ 5337 'getAllLocales': function() { 5338 return localeManager.getAll(); 5339 }, 5340 5341 /*** 5342 * @method getAllLocaleCodes() 5343 * @returns string[] 5344 * @static 5345 * @short Returns all available locale codes as an array of str
5345ings. 5346 * @extra For more, see `date locales`. 5347 * @example 5348 * 5349 * Date.getAllLocaleCodes() 5350 * 5351 ***/ 5352 'getAllLocaleCodes': function() { 5353 return getKeys(localeManager.getAll()); 5354 }, 5355 5356 /*** 5357 * @method setLocale(code) 5358 * @returns Locale 5359 * @static 5360 * @short Sets the current locale to be used with dates. 5361 * @extra Sugar has native support for 17 major locales. In addition, you can 5362 * define a new locale with `addLocale`. For more, see `date locales`. 5363 * @example 5364 * 5365 * Date.setLocale('en') 5366 * 5367 * @param {string} code 5368 * 5369 ***/ 5370 'setLocale': function(code) { 5371 return localeManager.set(code); 5372 }, 5373 5374 /*** 5375 * @method addLocale(code, def) 5376 * @returns Locale 5377 * @static 5378 * @short Adds a locale definition to the locales understood by Sugar. 5379 * @extra This method should only be required for adding locale definitions 5380 * that don't already exist. For more, see `date locales`. 5381 * @example 5382 * 5383 * Date.addLocale('eo', {}) 5384 * 5385 * @param {string} code 5386 * @param {Object} def 5387 * 5388 ***/ 5389 'addLocale': function(code, set) { 5390 return localeManager.add(code, set); 5391 }, 5392 5393 /*** 5394 * @method removeLocale(code) 5395 * @returns Locale 5396 * @static 5397 * @short Deletes the the locale by `code` from Sugar's known locales. 5398 * @extra For more, see `date locales`. 5399 * @example 5400 * 5401 * Date.removeLocale('foo') 5402 * 5403 * @param {string} code 5404 * 5405 ***/ 5406 'removeLocale': function(code) { 5407 return localeManager.remove(code); 5408 } 5409 5410 }); 5411 5412 defineInstanceWithArguments(sugarDate, { 5413 5414 /*** 5415 * @method set(set, [reset] = false) 5416 * @returns Date 5417 * @short Sets the date object. 5418 * @extra This method accepts multiple formats including a single number as 5419 * a timestamp, an object, or enumerated arguments. If [reset] is 5420 * `true`, any units more specific than those passed will be reset. 5421 * 5422 * @example 5423 * 5424 * new Date().set({year:2011,month:11,day:31}) -> December 31, 2011 5425 * new Date().set(2011, 11, 31) -> December 31, 2011 5426 * new Date().set(86400000) -> 1 day after Jan 1, 1970 5427 * new Date().set({year:2004,month:6}, true) -> June 1, 2004, 00:00:00.000 5428 * 5429 * @signature set(milliseconds) 5430 * @signature set(year, month, [day], [hour], [minute], [second], [millliseconds]) 5431 * @param {Object} set 5432 * @param {boolean} [reset] 5433 * @param {number} year 5434 * @param {number} month 5435 * @param {number} [day] 5436 * @param {number} [hour] 5437 * @param {number} [minute] 5438 * @param {number} [second] 5439 * @param {number} [milliseconds] 5440 * 5441 ***/ 5442 'set': function(d, args) { 5443 args = collectDateArguments(args); 5444 return updateDate(d, args[0], args[1]); 5445 }, 5446 5447 /*** 5448 * @method advance(set, [reset] = false) 5449 * @returns Date 5450 * @short Shifts the date forward. 5451 * @extra `set` accepts multiple formats including an object, a string in the 5452 * format "3 days", a single number as milliseconds, or enumerated 5453 * parameters (as with the Date constructor). If [reset] is `true`, any 5454 * units more specific than those passed will be reset. This method 5455 * modifies the date! 5456 * 5457 * @example 5458 * 5459 * new Date().advance({ year: 2 }) -> 2 years in the future 5460 * new Date().advance('2 hours') -> 2 hours in the future 5461 * new Date().advance(0, 2, 3) -> 2 months 3 days in the future 5462 * new Date().advance(86400000) -> 1 day in the future 5463 * 5464 * @signature advance(milliseconds) 5465 * @signature advance(year, month, [day], [hour], [minute], [second], [millliseconds]) 5466 * @param {string|Object} set 5467 * @param {boolean} [reset] 5468 * @param {number} year 5469 * @param {number} month 5470 * @param {number} [day] 5471 * @param {number} [hour] 5472 * @param {number} [minute] 5473 * @param {number} [second] 5474 * @param {number} [milliseconds] 5475 * 5476 ***/ 5477 'advance': function(d, args) { 5478 return advanceDateWithArgs(d, args, 1); 5479 }, 5480 5481 /*** 5482 * @method rewind(set, [reset] = false) 5483 * @returns Date 5484 * @short Shifts the date backward. 5485 * @extra [set] accepts multiple formats including an object, a string in the 5486 * format "3 days", a single number as milliseconds, or enumerated 5487 * parameters (as with the Date constructor). If [reset] is `true`, any 5488 * units more specific than those passed will be reset. This method 5489 * modifies the date! 5490 * 5491 * @example 5492 * 5493 * new Date().rewind({ year: 2 }) -> 2 years in the past 5494 * new Date().rewind('2 weeks') -> 2 weeks in the past 5495 * new Date().rewind(0, 2, 3) -> 2 months 3 days in the past 5496 * new Date().rewind(86400000) -> 1 day in the past 5497 * 5498 * @signature advance(milliseconds) 5499 * @signature advance(year, month, [day], [hour], [minute], [second], [millliseconds]) 5500 * @param {string|Object} set 5501 * @param {boolean} [reset] 5502 * @param {number} year 5503 * @param {number} month 5504 * @param {number} [day] 5505 * @param {number} [hour] 5506 * @param {number} [minute] 5507 * @param {number} [second] 5508 * @param {number} [milliseconds] 5509 * 5510 ***/ 5511 'rewind': function(d, args) { 5512 return advanceDateWithArgs(d, args, -1);
5513 } 5514 5515 }); 5516 5517 defineInstance(sugarDate, { 5518 5519 /*** 5520 * @method get(d, [options]) 5521 * @returns Date 5522 * @short Gets a new date using the current one as a starting point. 5523 * @extra This method is identical to `Date.create`, except that relative 5524 * formats like `next month` are relative to the date instance rather 5525 * than the current date. Accepts a locale code as a string in place 5526 * of [options]. See `create` for more. 5527 * 5528 * @example 5529 * 5530 * nextYear.get('monday') -> monday of the week exactly 1 year from now 5531 * millenium.get('2 years before') -> 2 years before Jan 1, 2000. 5532 * 5533 * @param {string|number|Date} d 5534 * @param {DateCreateOptions} options 5535 * 5536 ***/ 5537 'get': function(date, d, options) { 5538 return createDateWithContext(date, d, options); 5539 }, 5540 5541 /*** 5542 * @method setWeekday(dow) 5543 * @short Sets the weekday of the date, starting with Sunday at `0`. 5544 * @extra This method modifies the date! 5545 * 5546 * @example 5547 * 5548 * d = new Date(); d.setWeekday(1); d; -> Monday of this week 5549 * d = new Date(); d.setWeekday(6); d; -> Saturday of this week 5550 * 5551 * @param {number} dow 5552 * 5553 ***/ 5554 'setWeekday': function(date, dow) { 5555 return setWeekday(date, dow); 5556 }, 5557 5558 /*** 5559 * @method setISOWeek(num) 5560 * @short Sets the week (of the year) as defined by the ISO8601 standard. 5561 * @extra Note that this standard places Sunday at the end of the week (day 7). 5562 * This method modifies the date! 5563 * 5564 * @example 5565 * 5566 * d = new Date(); d.setISOWeek(15); d; -> 15th week of the year 5567 * 5568 * @param {number} num 5569 * 5570 ***/ 5571 'setISOWeek': function(date, num) { 5572 return setISOWeekNumber(date, num); 5573 }, 5574 5575 /*** 5576 * @method getISOWeek() 5577 * @returns Number 5578 * @short Gets the date's week (of the year) as defined by the ISO8601 standard. 5579 * @extra Note that this standard places Sunday at the end of the week (day 7). 5580 * If `utc` is set on the date, the week will be according to UTC time. 5581 * 5582 * @example 5583 * 5584 * new Date().getISOWeek() -> today's week of the year 5585 * 5586 ***/ 5587 'getISOWeek': function(date) { 5588 return getWeekNumber(date, true); 5589 }, 5590 5591 /*** 5592 * @method beginningOfISOWeek() 5593 * @returns Date 5594 * @short Set the date to the beginning of week as defined by ISO8601. 5595 * @extra Note that this standard places Monday at the start of the week. 5596 * This method modifies the date! 5597 * 5598 * @example 5599 * 5600 * new Date().beginningOfISOWeek() -> Monday 5601 * 5602 ***/ 5603 'beginningOfISOWeek': function(date) { 5604 var day = getWeekday(date); 5605 if (day === 0) { 5606 day = -6; 5607 } else if (day !== 1) { 5608 day = 1; 5609 } 5610 setWeekday(date, day); 5611 return resetTime(date); 5612 }, 5613 5614 /*** 5615 * @method endOfISOWeek() 5616 * @returns Date 5617 * @short Set the date to the end of week as defined by this ISO8601 standard. 5618 * @extra Note that this standard places Sunday at the end of the week. 5619 * This method modifies the date! 5620 * 5621 * @example 5622 * 5623 * new Date().endOfISOWeek() -> Sunday 5624 * 5625 ***/ 5626 'endOfISOWeek': function(date) { 5627 if (getWeekday(date) !== 0) { 5628 setWeekday(date, 7); 5629 } 5630 return moveToEndOfUnit(date, DAY_INDEX); 5631 }, 5632 5633 /*** 5634 * @method getUTCOffset([iso] = false) 5635 * @returns String 5636 * @short Returns a string representation of the offset from UTC time. If [iso] 5637 * is true the offset will be in ISO8601 format. 5638 * 5639 * @example 5640 * 5641 * new Date().getUTCOffset() -> "+0900" 5642 * new Date().getUTCOffset(true) -> "+09:00" 5643 * 5644 * @param {boolean} iso 5645 * 5646 ***/ 5647 'getUTCOffset': function(date, iso) { 5648 return getUTCOffset(date, iso); 5649 }, 5650 5651 /*** 5652 * @method setUTC([on] = false) 5653 * @returns Date
5654 * @short Controls a flag on the date that tells Sugar to internally use UTC 5655 * methods like `getUTCHours`. 5656 * @extra This flag is most commonly used for output in UTC time with the 5657 * `format` method. Note that this flag only governs which methods are 5658 * called internally â date native methods like `setHours` will still 5659 * return local non-UTC values. This method will modify the date! 5660 * 5661 * @example 5662 * 5663 * new Date().setUTC(true).long() -> formatted with UTC methods 5664 * new Date().setUTC(false).long() -> formatted without UTC methods 5665 * 5666 * @param {boolean} on 5667 * 5668 ***/ 5669 'setUTC': function(date, on) { 5670 return _utc(date, on); 5671 }, 5672 5673 /*** 5674 * @method isUTC() 5675 * @returns Boolean 5676 * @short Returns true if the date has no timezone offset. 5677 * @extra This will also return true for dates whose internal utc flag is set 5678 * with `setUTC`. Even if the utc flag is set, `getTimezoneOffset` 5679 * will always report the same thing as Javascript always reports that 5680 * based on the environment's locale. 5681 * 5682 * @example 5683 * 5684 * new Date().isUTC() -> true or false (depends on the local offset) 5685 * new Date().setUTC(true).isUTC() -> true 5686 * 5687 ***/ 5688 'isUTC': function(date) { 5689 return isUTC(date); 5690 }, 5691 5692 /*** 5693 * @method isValid() 5694 * @returns Boolean 5695 * @short Returns true if the date is valid. 5696 * 5697 * @example 5698 * 5699 * new Date().isValid() -> true 5700 * new Date('flexor').isValid() -> false 5701 * 5702 ***/ 5703 'isValid': function(date) { 5704 return dateIsValid(date); 5705 }, 5706 5707 /*** 5708 * @method isAfter(d, [margin] = 0) 5709 * @returns Boolean 5710 * @short Returns true if the date is after `d`. 5711 * @extra [margin] is to allow extra margin of error in ms. `d` will accept 5712 * a date object, timestamp, or string. If not specified, `d` is 5713 * assumed to be now. See `create` for formats. 5714 * 5715 * @example 5716 * 5717 * today.isAfter('tomorrow') -> false 5718 * today.isAfter('yesterday') -> true 5719 * 5720 * @param {string|number|Date} d 5721 * @param {number} [margin] 5722 * 5723 ***/ 5724 'isAfter': function(date, d, margin) { 5725 return date.getTime() > createDate(d).getTime() - (margin || 0); 5726 }, 5727 5728 /*** 5729 * @method isBefore(d, [margin] = 0) 5730 * @returns Boolean 5731 * @short Returns true if the date is before `d`. 5732 * @extra [margin] is to allow extra margin of error in ms. `d` will accept 5733 * a date object, timestamp, or text format. If not specified, `d` is 5734 * assumed to be now. See `create` for formats. 5735 * 5736 * @example 5737 * 5738 * today.isBefore('tomorrow') -> true 5739 * today.isBefore('yesterday') -> false 5740 * 5741 * @param {string|number|Date} d 5742 * @param {number} [margin] 5743 * 5744 ***/ 5745 'isBefore': function(date, d, margin) { 5746 return date.getTime() < createDate(d).getTime() + (margin || 0); 5747 }, 5748 5749 /*** 5750 * @method isBetween(d1, d2, [margin] = 0) 5751 * @returns Boolean 5752 * @short Returns true if the date is later or equal to `d1` and before or 5753 * equal to `d2`. 5754 * @extra [margin] is to allow extra margin of error in ms. `d1` and `d2` will 5755 * accept a date object, timestamp, or text format. If not specified, 5756 * they are assumed to be now. See `create` for formats. 5757 * 5758 * @example 5759 * 5760 * new Date().isBetween('yesterday', 'tomorrow') -> true 5761 * new Date().isBetween('last year', '2 years ago') -> false 5762 * 5763 * @param {string|number|Date} d1 5764 * @param {string|number|Date} d2 5765 * @param {number} [margin] 5766 * 5767 ***/ 5768 'isBetween': function(date, d1, d2, margin) { 5769 var t = date.getTime(); 5770 var t1 = createDate(d1).getTime(); 5771 var t2 = createDate(d2).getTime(); 5772 var lo = min(t1, t2); 5773 var hi = max(t1, t2); 5774 margin = margin || 0; 5775 return (lo - margin <= t) && (hi + margin >= t); 5776 }, 5777 5778 /*** 5779 * @method isLeapYear() 5780 * @returns Boolean 5781 * @short Returns true if the date is a leap year. 5782 * 5783 * @example 5784 * 5785 * millenium.isLeapYear() -> true 5786 * 5787 ***/ 5788 'isLeapYear': function(date) { 5789 var year = getYear(date); 5790 return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0); 5791 }, 5792 5793 /*** 5794 * @method daysInMonth() 5795 * @returns Number 5796 * @short Returns the number of days in the date's month. 5797 * 5798 * @example 5799 * 5800 * may.daysInMonth() -> 31 5801 * feb.daysInMonth() -> 28 or 29 5802 * 5803 ***/ 5804 'daysInMonth': function(date) { 5805 return getDaysInMonth(date); 5806 }, 5807 5808 /*** 5809 * @method format([f], [localeCode] = currentLocaleCode) 5810 * @returns String 5811 * @short Returns the date as a string using the format `f`. 5812 * @extra `f` is a string that contains tokens in either LDML format using 5813 * curly braces, or "strftime" format using a percent sign. If `f` is 5814 * not specified, the locale specific `{long}` format is used. [localeCode] 5815 * is a locale code to use (if not specified the current locale is 5816 * used). For more, see `date formatting`. 5817 * 5818 * @example 5819 * 5820 * new Date().format() -> ex. February 13, 2012 11:21 AM 5821 * new Date().format('{Weekday} {d} {Month}') -> ex. Monday July 4 5822 * new Date().format('{hh}:{mm}') -> ex. 15:57 5823 * new Date().format('%H:%M') -> ex. 15:57 5824 * new Date().format('{12hr}:{mm}{tt}') -> ex. 3:57pm 5825 * new Date().format('ISO8601') -> ex. 2011-07-05 12:24:55.528Z 5826 * new Date().format('{Weekday}', 'ja') -> ex. å é± 5827 * 5828 * @param {string} f 5829 * @param {string} [localeCode] 5830 * 5831 *** 5832 * @method short([localeCode] = currentLocaleCode) 5833 * @returns String 5834 * @short Outputs the date in the short format for the current locale. 5835 * @extra [localeCode] overrides the current locale code if passed. 5836 * 5837 * @example 5838 * 5839 * new Date().short() -> ex. 02/13/2016 5840 * new Date().short('fi') -> ex. 13.2.2016 5841 * 5842 * @param {string} [localeCode] 5843 * 5844 *** 5845 * @method medium([localeCode] = currentLocaleCode) 5846 * @returns String 5847 * @short Outputs the date in the medium format for the current locale. 5848 * @extra [localeCode] overrides the current locale code if passed. 5849 * 5850 * @example 5851 * 5852 * new Date().medium() -> ex. February 13, 2016 5853 * new Date().medium('ja') -> ex. 2016å¹´2æ13æ¥ 5854 * 5855 * @param {string} [localeCode] 5856 * 5857 *** 5858 * @method long([localeCode] = currentLocaleCode) 5859 * @returns String 5860 * @short Outputs the date in the long format for the current locale. 5861 * @extra [localeCode] overrides the current locale code if passed. 5862 * 5863 * @example 5864 * 5865 * new Date().long() -> ex. February 13, 2016 6:22 PM 5866 * new Date().long('es') -> ex. 13 de febrero de 2016 18:22 5867 * 5868 * @param {string} [localeCode] 5869 * 5870 *** 5871 * @method full([localeCode] = currentLocaleCode) 5872 * @returns String 5873 * @short Outputs the date in the full format for the current locale. 5874 * @extra [localeCode] overrides the current locale code if passed. 5875 * 5876 * @example 5877 * 5878 * new Date().full() -> ex. Saturday, February 13, 2016 6:23 PM 5879 * new Date().full('ru') -> ex. ÑÑббоÑа, 13 ÑевÑÐ°Ð»Ñ 2016 г., 18:23 5880 * 5881 * @param {string} [localeCode] 5882 * 5883 ***/ 5884 'format': function(date, f, localeCode) { 5885 return dateFormat(date, f, localeCode); 5886 }, 5887 5888 /*** 5889 * @method relative([localeCode] = currentLocaleCode, [fn]) 5890 * @returns String 5891 * @short Returns the date in a text format relative to the current time, 5892 * such as "5 minutes ago". 5893 * @extra [fn] is a function that can be passed to provide more granular 5894 * control over the resulting string. Its return value will be passed 5895 * to `format`. If nothing is returned, the relative format will be 5896 * used. [fn] may be passed as the first argument in place of [locale]. 5897 * For more about formats, see `date formatting`. 5898 * 5899 * @callback relativeFn 5900 * 5901 * num The offset number in `unit`. 5902 * unit A numeric representation of the unit that `num` is in, starting at 5903 * 0 for ms. 5904 * ms The absolute offset in milliseconds. 5905 * loc The locale object, either specified by [locale] or default. 5906 * 5907 * @example 5908 * 5909 * hourAgo.relative() -> 1 hour ago 5910 * jan.relative() -> ex. 5 months ago 5911 * jan.relative('ja') -> 3ã¶æå 5912 * jan.relative(function(num, unit, ms, loc) { 5913 * // Return an absolute date for anything over 6 months. 5914 * if(unit == 6 && num > 6 || unit > 6) { 5915 * return '{Month} {d}, {yyyy}'; 5916 * } 5917 * }); -> ex. 5 months ago 5918 * 5919 * @signature relative([fn]) 5920 * @param {string} [localeCode] 5921 * @param {relativeFn} [fn] 5922 * @callbackParam {number} num 5923 * @callbackParam {number} unit 5924 * @callbackParam {number} ms 5925 * @callbackParam {Locale} loc 5926 * @callbackReturns {string} relativeFn 5927 * 5928 ***/ 5929 'relative': function(date, localeCode, fn) { 5930 return dateRelative(date, null, localeCode, fn); 5931 }, 5932 5933 /*** 5934 * @method relativeTo(d, [localeCode] = currentLocaleCode) 5935 * @returns String 5936 * @short Returns the date in a text format relative to `d`, such as 5937 * "5 minutes". 5938 * @extra `d` will accept a date object, timestamp, or string. [localeCode] 5939 * applies to the method output, not `d`. 5940 * 5941 * @example 5942 * 5943 * jan.relativeTo(jul) -> 5 months 5944 * yesterday.relativeTo('today', 'ja') -> 䏿¥ 5945 * 5946 * @param {string|number|Date} d 5947 * @param {string} code 5948 * 5949 * 5950 ***/ 5951 'relativeTo': function(date, d, localeCode) { 5952 return dateRelative(date, createDate(d), localeCode); 5953 }, 5954 5955 /*** 5956 * @method is(d, [margin] = 0) 5957 * @returns Boolean 5958 * @short Returns true if the date matches `d`. 5959 * @extra `d` will accept a date object, timestamp, or text format. In the 5960 * case of objects and text formats, `is` will additionally compare 5961 * based on the precision implied in the input. In the case of text 5962 * formats `d` will use the currently set locale. [margin] allows an 5963 * extra margin of error in milliseconds. See `create` for formats. 5964 * 5965 * @example 5966 * 5967 * new Date().is('July') -> true or false? 5968 * new Date().is('1776') -> false 5969 * new Date().is('today') -> true 5970 * new Date().is('weekday') -> true or false? 5971 * new Date().is('July 4, 1776') -> false 5972 * new Date().is(-6106093200000) -> false 5973 * new Date().is(new Date(1776, 6, 4)) -> false 5974 * 5975 * @param {string|number|Date} d 5976 * @param {number} [margin] 5977 * 5978 ***/ 5979 'is': function(date, d, margin) { 5980 return fullCompareDate(date, d, margin); 5981 }, 5982 5983 /*** 5984 * @method reset([unit] = 'day', [localeCode] = currentLocaleCode) 5985 * @returns Date 5986 * @short Resets the date to the beginning of [unit]. 5987 * @extra This method effectively resets all smaller units, pushing the date 5988 * to the beginning of [unit]. Default is `day`, which effectively 5989 * resets the time. [localeCode] is provided for resetting weeks, which 5990 * is locale dependent. This method modifies the date! 5991 * 5992 * @example 5993 * 5994 * new Date().reset('day') -> Beginning of the day 5995 * new Date().reset('month') -> Beginning of the month 5996 * 5997 * @param {string} [unit] 5998 * @param {string} [localeCode] 5999 * 6000 ***/ 6001 'reset': function(date, unit, localeCode) { 6002 var unitIndex = unit ? getUnitIndexForParamName(unit) : DAY_INDEX; 6003 moveToBeginningOfUnit(date, unitIndex, localeCode); 6004 return date; 6005 }, 6006 6007 /*** 6008 * @method clone() 6009 * @returns Date 6010 * @short Clones the date. 6011 * @extra Note that the UTC flag will be preserved if set. This flag is 6012 * set via the `setUTC` method or an option on `Date.create`. 6013 * 6014 * @example 6015 * 6016 * new Date().clone() -> Copy of now 6017 * 6018 ***/ 6019 'clone': function(date) { 6020 return cloneDate(date); 6021 }, 6022 6023 /*** 6024 * @method iso() 6025 * @alias toISOString 6026 * 6027 ***/ 6028 'iso': function(date) { 6029 return date.toISOString(); 6030 }, 6031 6032 /*** 6033 * @method getWeekday() 6034 * @returns Number 6035 * @short Alias for `getDay`. 6036 * 6037 * @example 6038 * 6039 * new Date().getWeekday(); -> (ex.) 3 6040 * 6041 ***/ 6042 'getWeekday': function(date) { 6043 return getWeekday(date); 6044 }, 6045 6046 /*** 6047 * @method getUTCWeekday() 6048 * @returns Number 6049 * @short Alias for `getUTCDay`. 6050 * 6051 * @example 6052 * 6053 * new Date().getUTCWeekday(); -> (ex.) 3 6054 * 6055 ***/ 6056 'getUTCWeekday': function(date) { 6057 return date.getUTCDay(); 6058 } 6059 6060 });
6061 6062 6063 /*** @namespace Number ***/ 6064 6065 /*** 6066 * @method [dateUnit]() 6067 * @returns Number 6068 * @short Takes the number as a unit of time and converts to milliseconds. 6069 * @extra Method names can be singular or plural. Note that as "a month" is 6070 * ambiguous as a unit of time, `months` will be equivalent to 30.4375 6071 * days, the average number in a month. Be careful using `months` if you 6072 * need exact precision. 6073 * 6074 * @set 6075 * millisecond 6076 * milliseconds 6077 * second 6078 * seconds 6079 * minute 6080 * minutes 6081 * hour 6082 * hours 6083 * day 6084 * days 6085 * week 6086 * weeks 6087 * month 6088 * months 6089 * year 6090 * years 6091 * 6092 * @example 6093 * 6094 * (5).milliseconds() -> 5 6095 * (10).hours() -> 36000000 6096 * (1).day() -> 86400000 6097 * 6098 *** 6099 * @method [dateUnit]Before(d, [options]) 6100 * @returns Date 6101 * @short Returns a date that is `n` units before [d], where `n` is the number. 6102 * @extra [d] will accept a date object, timestamp, or text format. Note that 6103 * "months" is ambiguous as a unit of time. If the target date falls on a 6104 * day that does not exist (i.e. August 31 -> February 31), the date will 6105 * be shifted to the last day of the month. Be careful using 6106 * `monthsBefore` if you need exact precision. [options] can be an object 6107 * or a locale code as a string. See `create` for more. 6108 * 6109 * 6110 * @set 6111 * millisecondBefore 6112 * millisecondsBefore 6113 * secondBefore 6114 * secondsBefore 6115 * minuteBefore 6116 * minutesBefore 6117 * hourBefore 6118 * hoursBefore 6119 * dayBefore 6120 * daysBefore 6121 * weekBefore 6122 * weeksBefore 6123 * monthBefore 6124 * monthsBefore 6125 * yearBefore 6126 * yearsBefore 6127 * 6128 * @example 6129 * 6130 * (5).daysBefore('tuesday') -> 5 days before tuesday of this week 6131 * (1).yearBefore('January 23, 1997') -> January 23, 1996 6132 * 6133 * @param {string|number|Date} d 6134 * @param {DateCreateOptions} options 6135 * 6136 *** 6137 * @method [dateUnit]Ago() 6138 * @returns Date 6139 * @short Returns a date that is `n` units ago. 6140 * @extra Note that "months" is ambiguous as a unit of time. If the target date 6141 * falls on a day that does not exist (i.e. August 31 -> February 31), the 6142 * date will be shifted to the last day of the month. Be careful using 6143 * `monthsAgo` if you need exact precision. 6144 * 6145 * @set 6146 * millisecondAgo 6147 * millisecondsAgo 6148 * secondAgo 6149 * secondsAgo 6150 * minuteAgo 6151 * minutesAgo 6152 * hourAgo 6153 * hoursAgo 6154 * dayAgo 6155 * daysAgo 6156 * weekAgo 6157 * weeksAgo 6158 * monthAgo 6159 * monthsAgo 6160 * yearAgo 6161 * yearsAgo 6162 * 6163 * @example 6164 * 6165 * (5).weeksAgo() -> 5 weeks ago 6166 * (1).yearAgo() -> January 23, 1996 6167 * 6168 *** 6169 * @method [dateUnit]After(d, [options]) 6170 * @returns Date 6171 * @short Returns a date `n` units after [d], where `n` is the number. 6172 * @extra [d] will accept a date object, timestamp, or text format. Note that 6173 * "months" is ambiguous as a unit of time. If the target date falls on a 6174 * day that does not exist (i.e. August 31 -> February 31), the date will 6175 * be shifted to the last day of the month. Be careful using 6176 * `monthsAfter` if you need exact precision. [options] can be an object 6177 * or a locale code as a string. See `create` for more. 6178 * 6179 * @set 6180 * millisecondAfter 6181 * millisecondsAfter 6182 * secondAfter 6183 * secondsAfter 6184 * minuteAfter 6185 * minutesAfter 6186 * hourAfter 6187 * hoursAfter 6188 * dayAfter 6189 * daysAfter 6190 * weekAfter 6191 * weeksAfter 6192 * monthAfter 6193 * monthsAfter 6194 * yearAfter 6195 * yearsAfter 6196 * 6197 * @example 6198 * 6199 * (5).daysAfter('tuesday') -> 5 days after tuesday of this week 6200 * (1).yearAfter('January 23, 1997') -> January 23, 1998 6201 * 6202 * @param {string|number|Date} d 6203 * @param {DateCreateOptions} options 6204 * 6205 *** 6206 * @method [dateUnit]FromNow() 6207 * @returns Date 6208 * @short Returns a date `n` units from now. 6209 * @extra Note that "months" is ambiguous as a unit of time. If the target date 6210 * falls on a day that does not exist (i.e. August 31 -> February 31), the 6211 * date will be shifted to the last day of the month. Be careful using 6212 * `monthsFromNow` if you need exact precision. 6213 * 6214 * @set
6215 * millisecondFromNow 6216 * millisecondsFromNow 6217 * secondFromNow 6218 * secondsFromNow 6219 * minuteFromNow 6220 * minutesFromNow 6221 * hourFromNow 6222 * hoursFromNow 6223 * dayFromNow 6224 * daysFromNow 6225 * weekFromNow 6226 * weeksFromNow 6227 * monthFromNow 6228 * monthsFromNow 6229 * yearFromNow 6230 * yearsFromNow 6231 * 6232 * @example 6233 * 6234 * (5).weeksFromNow() -> 5 weeks ago 6235 * (1).yearFromNow() -> January 23, 1998 6236 * 6237 ***/ 6238 function buildNumberUnitMethods() { 6239 defineInstanceSimilar(sugarNumber, DateUnits, function(methods, unit) { 6240 var name = unit.name, base, after, before; 6241 base = function(n) { 6242 return round(n * unit.multiplier); 6243 }; 6244 after = function(n, d, options) { 6245 return advanceDate(createDate(d, options, true), name, n); 6246 }; 6247 before = function(n, d, options) { 6248 return advanceDate(createDate(d, options, true), name, -n); 6249 }; 6250 methods[name] = base; 6251 methods[name + 's'] = base; 6252 methods[name + 'Before'] = before; 6253 methods[name + 'sBefore'] = before; 6254 methods[name + 'Ago'] = before; 6255 methods[name + 'sAgo'] = before; 6256 methods[name + 'After'] = after; 6257 methods[name + 'sAfter'] = after; 6258 methods[name + 'FromNow'] = after; 6259 methods[name + 'sFromNow'] = after; 6260 }); 6261 } 6262 6263 defineInstance(sugarNumber, { 6264 6265 /*** 6266 * @method duration([localeCode] = currentLocaleCode) 6267 * @returns String 6268 * @short Takes the number as milliseconds and returns a localized string. 6269 * @extra This method is the same as `Date#relative` without the localized 6270 * equivalent of "from now" or "ago". [localeCode] can be passed as the 6271 * first (and only) parameter. Note that this method is only available 6272 * when the dates module is included. 6273 * 6274 * @example 6275 * 6276 * (500).duration() -> '500 milliseconds' 6277 * (1200).duration() -> '1 second' 6278 * (75).minutes().duration() -> '1 hour' 6279 * (75).minutes().duration('es') -> '1 hora' 6280 * 6281 * @param {string} [localeCode] 6282 * 6283 ***/ 6284 'duration': function(n, localeCode) { 6285 return localeManager.get(localeCode).getDuration(n); 6286 } 6287 6288 }); 6289 6290 6291 var EnglishLocaleBaseDefinition = { 6292 'code': 'en', 6293 'plural': true, 6294 'timeMarkers': 'at', 6295 'ampm': 'AM|A.M.|a,PM|P.M.|p', 6296 'units': 'millisecond:|s,second:|s,minute:|s,hour:|s,day:|s,week:|s,month:|s,year:|s', 6297 'months': 'Jan:uary|,Feb:ruary|,Mar:ch|,Apr:il|,May,Jun:e|,Jul:y|,Aug:ust|,Sep:tember|t|,Oct:ober|,Nov:ember|,Dec:ember|', 6298 'weekdays': 'Sun:day|,Mon:day|,Tue:sday|,Wed:nesday|,Thu:rsday|,Fri:day|,Sat:urday|+weekend', 6299 'numerals': 'zero,one|first,two|second,three|third,four:|th,five|fifth,six:|th,seven:|th,eight:|h,nin:e|th,ten:|th', 6300 'articles': 'a,an,the', 6301 'tokens': 'the,st|nd|rd|th,of|in,a|an,on', 6302 'time': '{H}:{mm}', 6303 'past': '{num} {unit} {sign}', 6304 'future': '{num} {unit} {sign}', 6305 'duration': '{num} {unit}', 6306 'modifiers': [ 6307 { 'name': 'half', 'src': 'half', 'value': .5 }, 6308 { 'name': 'midday', 'src': 'noon', 'value': 12 }, 6309 { 'name': 'midday', 'src': 'midnight', 'value': 24 }, 6310 { 'name': 'day', 'src': 'yesterday', 'value': -1 }, 6311 { 'name': 'day', 'src': 'today|tonight', 'value': 0 }, 6312 { 'name': 'day', 'src': 'tomorrow', 'value': 1 }, 6313 { 'name': 'sign', 'src': 'ago|before', 'value': -1 }, 6314 { 'name': 'sign', 'src': 'from now|after|from|in|later', 'value': 1 }, 6315 { 'name': 'edge', 'src': 'first day|first|beginning', 'value': -2 }, 6316 { 'name': 'edge', 'src': 'last day', 'value': 1 }, 6317 { 'name': 'edge', 'src': 'end|last', 'value': 2 }, 6318 { 'name': 'shift', 'src': 'last', 'value': -1 }, 6319 { 'name': 'shift', 'src': 'the|this', 'value': 0 }, 6320 { 'name': 'shift', 'src': 'next', 'value': 1 } 6321 ], 6322 'parse': [ 6323 '(?:just)? now', 6324 '{shift} {unit:5-7}', 6325 "{months?} (?:{year}|'{yy})", 6326 '{midday} {4?} {day|weekday}', 6327 '{months},?(?:[-.\\/\\s]{year})?', 6328 '{edge} of (?:day)? {day|weekday}', 6329 '{0} {num}{1?} {weekday} {2} {months},? {year?}', 6330 '{shift?} {day?} {weekday?} {timeMarker?} {midday}', 6331 '{sign?} {3?} {half} {3?} {unit:3-4|unit:7} {sign?}', 6332 '{0?} {edge} {weekday?} {2} {shift?} {unit:4-7?} {months?},? {year?}' 6333 ], 6334 'timeParse': [ 6335 '{day|weekday}', 6336 '{shift} {unit:5?} {weekday}', 6337 '{0?} {date}{1?} {2?} {months?}', 6338 '{weekday} {2?} {shift} {unit:5}', 6339 '{0?} {num} {2?} {months}\\.?,? {year?}', 6340 '{num?} {unit:4-5} {sign} {day|weekday}', 6341 '{year}[-.\\/\\s]{months}[-.\\/\\s]{date}', 6342 '{0|months} {date?}{1?} of {shift} {unit:6-7}', 6343 '{0?} {num}{1?} {weekday} of {shift} {unit:6}', 6344 "{date}[-.\\/\\s]{months}[-.\\/\\s](?:{year}|'?{yy}
6344)", 6345 "{weekday?}\\.?,? {months}\\.?,? {date}{1?},? (?:{year}|'{yy})?" 6346 ], 6347 'timeFrontParse': [ 6348 '{sign} {num} {unit}', 6349 '{num} {unit} {sign}', 6350 '{4?} {day|weekday}' 6351 ] 6352 }; 6353 6354 var AmericanEnglishDefinition = getEnglishVariant({ 6355 'mdy': true, 6356 'firstDayOfWeek': 0, 6357 'firstDayOfWeekYear': 1, 6358 'short': '{MM}/{dd}/{yyyy}', 6359 'medium': '{Month} {d}, {yyyy}', 6360 'long': '{Month} {d}, {yyyy} {time}', 6361 'full': '{Weekday}, {Month} {d}, {yyyy} {time}', 6362 'stamp': '{Dow} {Mon} {d} {yyyy} {time}', 6363 'time': '{h}:{mm} {TT}' 6364 }); 6365 6366 var BritishEnglishDefinition = getEnglishVariant({ 6367 'short': '{dd}/{MM}/{yyyy}', 6368 'medium': '{d} {Month} {yyyy}', 6369 'long': '{d} {Month} {yyyy} {H}:{mm}', 6370 'full': '{Weekday}, {d} {Month}, {yyyy} {time}', 6371 'stamp': '{Dow} {d} {Mon} {yyyy} {time}' 6372 }); 6373 6374 var CanadianEnglishDefinition = getEnglishVariant({ 6375 'short': '{yyyy}-{MM}-{dd}', 6376 'medium': '{d} {Month}, {yyyy}', 6377 'long': '{d} {Month}, {yyyy} {H}:{mm}', 6378 'full': '{Weekday}, {d} {Month}, {yyyy} {time}', 6379 'stamp': '{Dow} {d} {Mon} {yyyy} {time}' 6380 }); 6381 6382 var LazyLoadedLocales = { 6383 'en-US': AmericanEnglishDefinition, 6384 'en-GB': BritishEnglishDefinition, 6385 'en-AU': BritishEnglishDefinition, 6386 'en-CA': CanadianEnglishDefinition 6387 }; 6388 6389 buildLocales(); 6390 buildDateFormatTokens(); 6391 buildDateFormatMatcher(); 6392 buildDateUnitMethods(); 6393 buildNumberUnitMethods(); 6394 buildRelativeAliases(); 6395 setDateChainableConstructor(); 6396 6397 /*** 6398 * @module String 6399 * @description String manupulation, encoding, truncation, and formatting, and more. 6400 * 6401 ***/ 6402 6403 // Flag allowing native string methods to be enhanced 6404 var STRING_ENHANCEMENTS_FLAG = 'enhanceString'; 6405 6406 // Matches non-punctuation characters except apostrophe for capitalization. 6407 var CAPITALIZE_REG = /[^\u0000-\u0040\u005B-\u0060\u007B-\u007F]+('s)?/g; 6408 6409 // Regex matching camelCase. 6410 var CAMELIZE_REG = /(^|_)([^_]+)/g; 6411 6412 // Regex matching any HTML entity. 6413 var HTML_ENTITY_REG = /&#?(x)?([\w\d]{0,5});/gi; 6414 6415 // Very basic HTML escaping regex. 6416 var HTML_ESCAPE_REG = /[&<>]/g; 6417 6418 // Special HTML entities. 6419 var HTMLFromEntityMap = { 6420 'lt': '<', 6421 'gt': '>', 6422 'amp': '&', 6423 'nbsp': ' ', 6424 'quot': '"', 6425 'apos': "'" 6426 }; 6427 6428 var HTMLToEntityMap; 6429 6430 // Words that should not be capitalized in titles 6431 var DOWNCASED_WORDS = [ 6432 'and', 'or', 'nor', 'a', 'an', 'the', 'so', 'but', 'to', 'of', 'at', 6433 'by', 'from', 'into', 'on', 'onto', 'off', 'out', 'in', 'over', 6434 'with', 'for' 6435 ]; 6436 6437 // HTML tags that do not have inner content. 6438 var HTML_VOID_ELEMENTS = [ 6439 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 6440 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr' 6441 ]; 6442 6443 var LEFT_TRIM_REG = RegExp('^[' + TRIM_CHARS + ']+'); 6444 var RIGHT_TRIM_REG = RegExp('[' + TRIM_CHARS + ']+$'); 6445 var TRUNC_REG = RegExp('(?=[' + TRIM_CHARS + '])'); 6446 6447 // Reference to native String#includes to enhance later. 6448 var nativeIncludes = String.prototype.includes; 6449 6450 // Base64 6451 var encodeBase64, decodeBase64; 6452 6453 // Format matcher for String#format. 6454 var stringFormatMatcher = createFormatMatcher(deepGetProperty); 6455 6456 function padString(num, padding) { 6457 return repeatString(isDefined(padding) ? padding : ' ', num); 6458 } 6459 6460 function truncateString(str, length, from, ellipsis, split) { 6461 var str1, str2, len1, len2; 6462 if (str.length <= length) { 6463 return str.toString(); 6464 } 6465 ellipsis = isUndefined(ellipsis) ? '...' : ellipsis; 6466 switch (from) { 6467 case 'left': 6468 str2 = split ? truncateOnWord(str, length, true) : str.slice(str.length - length); 6469 return ellipsis + str2; 6470 case 'middle': 6471 len1 = ceil(length / 2); 6472 len2 = floor(length / 2); 6473 str1 = split ? truncateOnWord(str, len1) : str.slice(0, len1); 6474 str2 = split ? truncateOnWord(str, len2, true) : str.slice(str.length - len2); 6475 return str1 + ellipsis + str2; 6476 default: 6477 str1 = split ? truncateOnWord(str, length) : str.slice(0, length); 6478 return str1 + ellipsis; 6479 } 6480 } 6481 6482 function stringEach(str, search, fn) { 6483 var chunks, chunk, reg, result = []; 6484 if (isFunction(search)) { 6485 fn = search; 6486 reg = /[\s\S]/g; 6487 } else if (!search) { 6488 reg = /[\s\S]/g; 6489 } else if (isString(search)) { 6490 reg = RegExp(escapeRegExp(search), 'gi'); 6491 } else if (isRegExp(search)) { 6492 reg = RegExp(search.source, getRegExpFlags(search, 'g')); 6493 } 6494 // Getting the entire array of chunks up front as we need to 6495 // pass this into the callback function as an argument. 6496 chunks = runGlobalMatch(str, reg); 6497 6498 if (chunks) { 6499 for (var i = 0, len = chunks.length, r; i < len; i++) { 6500 chunk = chunks[i]; 6501 result[i] = chunk; 6502 if (fn) { 6503 r = fn.call(str, chunk, i, chunks); 6504 if (r === false) { 6505 break; 6506 } else if (isDefined(r)) { 6507 result[i] = r; 6508 } 6509 } 6510 } 6511 } 6512 return result; 6513 } 6514 6515 // "match" in < IE9 has enumable properties that will confuse for..in 6516 // loops, so ensure that the match is a normal array by manually running 6517 // "exec". Note that this method is also slightly more performant. 6518 function runGlobalMatch(str, reg) { 6519 var result = [], match, lastLastIndex; 6520 while ((match = reg.exec(str)) != null) { 6521 if (reg.lastIndex === lastLastIndex) { 6522 reg.lastIndex += 1;
6523 } else { 6524 result.push(match[0]); 6525 } 6526 lastLastIndex = reg.lastIndex; 6527 } 6528 return result; 6529 } 6530 6531 function eachWord(str, fn) { 6532 return stringEach(trim(str), /\S+/g, fn); 6533 } 6534 6535 function stringCodes(str, fn) { 6536 var codes = new Array(str.length), i, len; 6537 for (i = 0, len = str.length; i < len; i++) { 6538 var code = str.charCodeAt(i); 6539 codes[i] = code; 6540 if (fn) { 6541 fn.call(str, code, i, str); 6542 } 6543 } 6544 return codes; 6545 } 6546 6547 function stringUnderscore(str) { 6548 var areg = Inflections.acronyms && Inflections.acronyms.reg; 6549 return str 6550 .replace(/[-\s]+/g, '_') 6551 .replace(areg, function(acronym, index) { 6552 return (index > 0 ? '_' : '') + acronym.toLowerCase(); 6553 }) 6554 .replace(/([A-Z\d]+)([A-Z][a-z])/g, '$1_$2') 6555 .replace(/([a-z\d])([A-Z])/g, '$1_$2') 6556 .toLowerCase(); 6557 } 6558 6559 function stringCamelize(str, upper) { 6560 str = stringUnderscore(str); 6561 return str.replace(CAMELIZE_REG, function(match, pre, word, index) { 6562 var cap = upper !== false || index > 0, acronym; 6563 acronym = getAcronym(word); 6564 if (acronym && cap) { 6565 return acronym; 6566 } 6567 return cap ? stringCapitalize(word, true) : word; 6568 }); 6569 } 6570 6571 function stringSpacify(str) { 6572 return stringUnderscore(str).replace(/_/g, ' '); 6573 } 6574 6575 function stringCapitalize(str, downcase, all) { 6576 if (downcase) { 6577 str = str.toLowerCase(); 6578 } 6579 return all ? str.replace(CAPITALIZE_REG, simpleCapitalize) : simpleCapitalize(str); 6580 } 6581 6582 function stringTitleize(str) { 6583 var fullStopPunctuation = /[.:;!]$/, lastHadPunctuation; 6584 str = runHumanRules(str); 6585 str = stringSpacify(str); 6586 return eachWord(str, function(word, index, words) { 6587 word = getHumanWord(word) || word; 6588 word = getAcronym(word) || word; 6589 var hasPunctuation, isFirstOrLast; 6590 var first = index == 0, last = index == words.length - 1; 6591 hasPunctuation = fullStopPunctuation.test(word); 6592 isFirstOrLast = first || last || hasPunctuation || lastHadPunctuation; 6593 lastHadPunctuation = hasPunctuation; 6594 if (isFirstOrLast || indexOf(DOWNCASED_WORDS, word) === -1) { 6595 return stringCapitalize(word, false, true); 6596 } else { 6597 return word; 6598 } 6599 }).join(' '); 6600 } 6601 6602 function stringParameterize(str, separator) { 6603 if (separator === undefined) separator = '-'; 6604 str = str.replace(/[^a-z0-9\-_]+/gi, separator); 6605 if (separator) { 6606 var reg = RegExp('^{s}+|{s}+$|({s}){s}+'.split('{s}').join(escapeRegExp(separator)), 'g'); 6607 str = str.replace(reg, '$1'); 6608 } 6609 return encodeURI(str.toLowerCase()); 6610 } 6611 6612 function reverseString(str) { 6613 return str.split('').reverse().join(''); 6614 } 6615 6616 function truncateOnWord(str, limit, fromLeft) { 6617 if (fromLeft) { 6618 return reverseString(truncateOnWord(reverseString(str), limit)); 6619 } 6620 var words = str.split(TRUNC_REG); 6621 var count = 0; 6622 return filter(words, function(word) { 6623 count += word.length; 6624 return count <= limit; 6625 }).join(''); 6626 } 6627 6628 function unescapeHTML(str) { 6629 return str.replace(HTML_ENTITY_REG, function(full, hex, code) { 6630 var special = HTMLFromEntityMap[code]; 6631 return special || chr(hex ? parseInt(code, 16) : +code); 6632 }); 6633 } 6634 6635 function tagIsVoid(tag) { 6636 return indexOf(HTML_VOID_ELEMENTS, tag.toLowerCase()) !== -1; 6637 } 6638 6639 function stringReplaceAll(str, f, replace) { 6640 var i = 0, tokens; 6641 if (isString(f)) { 6642 f = RegExp(escapeRegExp(f), 'g'); 6643 } else if (f && !f.global) { 6644 f = RegExp(f.source, getRegExpFlags(f, 'g')); 6645 } 6646 if (!replace) { 6647 replace = ''; 6648 } else { 6649 tokens = replace; 6650 replace = function() { 6651 var t = tokens[i++]; 6652 return t != null ? t : ''; 6653 }; 6654 } 6655 return str.replace(f, replace); 6656 } 6657 6658 function replaceTags(str, find, replacement, strip) { 6659 var tags = isString(find) ? [find] : find, reg, src; 6660 tags = map(tags || [], function(t) { 6661 return escapeRegExp(t); 6662 }).join('|'); 6663 src = tags.replace('all', '') || '[^\\s>]+'; 6664 src = '<(\\/)?(' + src + ')(\\s+[^<>]*?)?\\s*(\\/)?>'; 6665 reg = RegExp(src, 'gi'); 6666 return runTagReplacements(str.toString(), reg, strip, replacement); 6667 } 6668 6669 function runTagReplacements(str, reg, strip, replacement, fullString) { 6670 6671 var match; 6672 var result = ''; 6673 var currentIndex = 0; 6674 var openTagName; 6675 var openTagAttributes; 6676 var openTagCount = 0; 6677 6678 function processTag(index, tagName, attributes, tagLength, isVoid) { 6679 var content = str.slice(currentIndex, index), s = '', r = ''; 6680 if (isString(replacement)) { 6681 r = replacement; 6682 } else if (replacement) { 6683 r = replacement.call(fullString, tagName, content, attributes, fullString) || ''; 6684 } 6685 if (strip) { 6686 s = r; 6687 } else { 6688 content = r; 6689 } 6690 if (content) { 6691 content = runTagReplacements(content, reg, strip, replacement, fullString); 6692 } 6693 result += s + content + (isVoid ? '' : s); 6694 currentIndex = index + (tagLength || 0); 6695 } 6696 6697 fullString = fullString || str; 6698 reg = RegExp(reg.source, 'gi'); 6699 6700 while (match = reg.exec(str)) { 6701 6702 var tagName = match[2]; 6703 var attributes = (match[3] || '').slice(1); 6704 var isClosingTag = !!match[1]; 6705 var isSelfClosing = !!match[4]; 6706 var tagLength = match[0].length; 6707 var isVoid = tagIsVoid(tagName); 6708 var isOpeningTag = !isClosingTag && !isSelfClosing && !isVoid; 6709 var isSameAsCurrent = tagName === openTagName; 6710 6711 if (!openTagName) { 6712 result += str.slice(currentIndex, match.index); 6713 currentIndex = match.index; 6714 } 6715 6716 if (isOpeningTag) { 6717 if (!openTagName) { 6718 openTagName = tagName; 6719 openTagAttributes = attributes; 6720 openTagCount++; 6721 currentIndex += tagLength; 6722 } else if (isSameAsCurrent) { 6723 openTagCount++; 6724 } 6725 } else if (isClosingTag && isSameAsCurrent) { 6726 openTagCount--; 6727 if (openTagCount === 0) { 6728 processTag(match.index, openTagName, openTagAttributes, tagLength, isVoid); 6729 openTagName = null; 6730 openTagAttributes = null; 6731 } 6732 } else if (!openTagName) { 6733 processTag(match.index, tagName, attributes, tagLength, isVoid); 6734 } 6735 } 6736 if (openTagName) { 6737 processTag(str.length, openTagName, openTagAttributes); 6738 } 6739 result += str.slice(currentIndex); 6740 return result; 6741 } 6742 6743 function numberOrIndex(str, n, from) { 6744 if (isString(n)) { 6745 n = str.indexOf(n); 6746 if (n === -1) { 6747 n = from ? str.length : 0; 6748 } 6749 } 6750 return n; 6751 } 6752 6753 function buildBase64() { 6754 var encodeAscii, decodeAscii; 6755 6756 function catchEncodingError(fn) { 6757 return function(str) { 6758 try { 6759 return fn(str); 6760 } catch (e) { 6761 return ''; 6762 } 6763 }; 6764 } 6765 6766 if (typeof Buffer !== 'undefined') { 6767 encodeBase64 = function(str) { 6768 return new Buffer(str).toString('base64'); 6769 }; 6770 decodeBase64 = function(str) { 6771 return new Buffer(str, 'base64').toString('utf8'); 6772 }; 6773 return; 6774 } 6775 if (typeof btoa !== 'undefined') { 6776 encodeAscii = catchEncodingError(btoa); 6777 decodeAscii = catchEncodingError(atob); 6778 } else { 6779 var key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
6779+/='; 6780 var base64reg = /[^A-Za-z0-9\+\/\=]/g; 6781 encodeAscii = function(str) { 6782 var output = ''; 6783 var chr1, chr2, chr3; 6784 var enc1, enc2, enc3, enc4; 6785 var i = 0; 6786 do { 6787 chr1 = str.charCodeAt(i++); 6788 chr2 = str.charCodeAt(i++); 6789 chr3 = str.charCodeAt(i++); 6790 enc1 = chr1 >> 2; 6791 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 6792 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 6793 enc4 = chr3 & 63; 6794 if (isNaN(chr2)) { 6795 enc3 = enc4 = 64; 6796 } else if (isNaN(chr3)) { 6797 enc4 = 64; 6798 } 6799 output += key.charAt(enc1); 6800 output += key.charAt(enc2); 6801 output += key.charAt(enc3); 6802 output += key.charAt(enc4); 6803 chr1 = chr2 = chr3 = ''; 6804 enc1 = enc2 = enc3 = enc4 = ''; 6805 } while (i < str.length); 6806 return output; 6807 }; 6808 decodeAscii = function(input) { 6809 var output = ''; 6810 var chr1, chr2, chr3; 6811 var enc1, enc2, enc3, enc4; 6812 var i = 0; 6813 if (input.match(base64reg)) { 6814 return ''; 6815 } 6816 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); 6817 do { 6818 enc1 = key.indexOf(input.charAt(i++)); 6819 enc2 = key.indexOf(input.charAt(i++)); 6820 enc3 = key.indexOf(input.charAt(i++)); 6821 enc4 = key.indexOf(input.charAt(i++)); 6822 chr1 = (enc1 << 2) | (enc2 >> 4); 6823 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 6824 chr3 = ((enc3 & 3) << 6) | enc4; 6825 output = output + chr(chr1); 6826 if (enc3 != 64) { 6827 output = output + chr(chr2); 6828 } 6829 if (enc4 != 64) { 6830 output = output + chr(chr3); 6831 } 6832 chr1 = chr2 = chr3 = ''; 6833 enc1 = enc2 = enc3 = enc4 = ''; 6834 } while (i < input.length); 6835 return output; 6836 }; 6837 } 6838 encodeBase64 = function(str) { 6839 return encodeAscii(unescape(encodeURIComponent(str))); 6840 }; 6841 decodeBase64 = function(str) { 6842 return decodeURIComponent(escape(decodeAscii(str))); 6843 }; 6844 } 6845 6846 function buildEntities() { 6847 HTMLToEntityMap = {}; 6848 forEachProperty(HTMLFromEntityMap, function(val, key) { 6849 HTMLToEntityMap[val] = '&' + key + ';'; 6850 }); 6851 } 6852 6853 function callIncludesWithRegexSupport(str, search, position) { 6854 if (!isRegExp(search)) { 6855 return nativeIncludes.call(str, search, position); 6856 } 6857 if (position) { 6858 str = str.slice(position); 6859 } 6860 return search.test(str); 6861 } 6862 6863 defineInstance(sugarString, { 6864 6865 // Enhancment to String#includes to allow a regex. 6866 'includes': fixArgumentLength(callIncludesWithRegexSupport) 6867 6868 }, [ENHANCEMENTS_FLAG, STRING_ENHANCEMENTS_FLAG]); 6869 6870 defineInstance(sugarString, { 6871 6872 /*** 6873 * @method at(index, [loop] = false) 6874 * @returns Mixed 6875 * @short Gets the character(s) at a given index. 6876 * @extra When [loop] is true, overshooting the end of the string will begin 6877 * counting from the other end. `index` may be negative. If `index` is 6878 * an array, multiple elements will be returned. 6879 * @example 6880 * 6881 * 'jumpy'.at(0) -> 'j' 6882 * 'jumpy'.at(2) -> 'm' 6883 * 'jumpy'.at(5) -> '' 6884 * 'jumpy'.at(5, true) -> 'j' 6885 * 'jumpy'.at(-1) -> 'y' 6886 * 'lucky charms'.at([2, 4]) -> ['u','k'] 6887 * 6888 * @param {number|Array<number>} index 6889 * @param {boolean} [loop] 6890 * 6891 ***/ 6892 'at': function(str, index, loop) { 6893 return getEntriesForIndexes(str, index, loop, true); 6894 }, 6895 6896 /*** 6897 * @method escapeURL([param] = false) 6898 * @returns String 6899 * @short Escapes characters in a string to make a valid URL.
6900 * @extra If [param] is true, it will also escape valid URL characters. Use 6901 * this when the entire string is meant for use in a query string. 6902 * 6903 * @example 6904 * 6905 * 'a, b, and c'.escapeURL() -> 'a,%20b,%20and%20c' 6906 * 'http://foo.com/'.escapeURL(true) -> 'http%3A%2F%2Ffoo.com%2F' 6907 * 6908 * @param {boolean} [param] 6909 * 6910 ***/ 6911 'escapeURL': function(str, param) { 6912 return param ? encodeURIComponent(str) : encodeURI(str); 6913 }, 6914 6915 /*** 6916 * @method unescapeURL([partial] = false) 6917 * @returns String 6918 * @short Restores escaped characters in a URL escaped string. 6919 * @extra If [partial] is true, it will only unescape non-valid URL tokens, 6920 * and is included here for completeness, but should be rarely needed. 6921 * 6922 * @example 6923 * 6924 * 'http%3A%2F%2Ffoo.com%2F'.unescapeURL() -> 'http://foo.com/' 6925 * 'http%3A%2F%2Ffoo.com%2F'.unescapeURL(true) -> 'http%3A%2F%2Ffoo.com%2F' 6926 * 6927 * @param {boolean} [partial] 6928 * 6929 ***/ 6930 'unescapeURL': function(str, param) { 6931 return param ? decodeURI(str) : decodeURIComponent(str); 6932 }, 6933 6934 /*** 6935 * @method escapeHTML() 6936 * @returns String 6937 * @short Converts HTML characters to their entity equivalents. 6938 * 6939 * @example 6940 * 6941 * '<p>some text</p>'.escapeHTML() -> '<p>some text</p>' 6942 * 'one & two'.escapeHTML() -> 'one & two' 6943 * 6944 ***/ 6945 'escapeHTML': function(str) { 6946 return str.replace(HTML_ESCAPE_REG, function(chr) { 6947 return getOwn(HTMLToEntityMap, chr); 6948 }); 6949 }, 6950 6951 /*** 6952 * @method unescapeHTML() 6953 * @returns String 6954 * @short Restores escaped HTML characters. 6955 * 6956 * @example 6957 * 6958 * '<p>some text</p>'.unescapeHTML() -> '<p>some text</p>' 6959 * 'one & two'.unescapeHTML() -> 'one & two' 6960 * 6961 ***/ 6962 'unescapeHTML': function(str) { 6963 return unescapeHTML(str); 6964 }, 6965 6966 /*** 6967 * @method stripTags([tag] = 'all', [replace]) 6968 * @returns String 6969 * @short Strips HTML tags from the string. 6970 * @extra [tag] may be an array of tags or 'all', in which case all tags will 6971 * be stripped. [replace] will replace what was stripped, and may be a 6972 * string or a function to handle replacements. If this function returns 6973 * a string, then it will be used for the replacement. If it returns 6974 * `undefined`, the tags will be stripped normally. 6975 * 6976 * @callback tagReplaceFn 6977 * 6978 * tag The tag name. 6979 * inner The tag content. 6980 * attr The attributes on the tag, if any, as a string. 6981 * outer The entire matched tag string. 6982 * 6983 * @example 6984 * 6985 * '<p>just <b>some</b> text</p>'.stripTags() -> 'just some text' 6986 * '<p>just <b>some</b> text</p>'.stripTags('p') -> 'just <b>some</b> text' 6987 * '<p>hi!</p>'.stripTags('p', function(all, content) { 6988 * return '|'; 6989 * }); -> '|hi!|' 6990 * 6991 * @param {string} tag 6992 * @param {string|tagReplaceFn} replace 6993 * @callbackParam {string} tag 6994 * @callbackParam {string} inner 6995 * @callbackParam {string} attr 6996 * @callbackParam {string} outer 6997 * @callbackReturns {string} tagReplaceFn 6998 * 6999 ***/ 7000 'stripTags': function(str, tag, replace) { 7001 return replaceTags(str, tag, replace, true); 7002 }, 7003 7004 /*** 7005 * @method removeTags([tag] = 'all', [replace]) 7006 * @returns String 7007 * @short Removes HTML tags and their contents from the string. 7008 * @extra [tag] may be an array of tags or 'all', in which case all tags will 7009 * be removed. [replace] will replace what was removed, and may be a
7010 * string or a function to handle replacements. If this function returns 7011 * a string, then it will be used for the replacement. If it returns 7012 * `undefined`, the tags will be removed normally. 7013 * 7014 * @callback tagReplaceFn 7015 * 7016 * tag The tag name. 7017 * inner The tag content. 7018 * attr The attributes on the tag, if any, as a string. 7019 * outer The entire matched tag string. 7020 * 7021 * @example 7022 * 7023 * '<p>just <b>some</b> text</p>'.removeTags() -> '' 7024 * '<p>just <b>some</b> text</p>'.removeTags('b') -> '<p>just text</p>' 7025 * '<p>hi!</p>'.removeTags('p', function(all, content) { 7026 * return 'bye!'; 7027 * }); -> 'bye!' 7028 * 7029 * @param {string} tag 7030 * @param {string|tagReplaceFn} replace 7031 * @callbackParam {string} tag 7032 * @callbackParam {string} inner 7033 * @callbackParam {string} attr 7034 * @callbackParam {string} outer 7035 * @callbackReturns {string} tagReplaceFn 7036 * 7037 ***/ 7038 'removeTags': function(str, tag, replace) { 7039 return replaceTags(str, tag, replace, false); 7040 }, 7041 7042 /*** 7043 * @method encodeBase64() 7044 * @returns String 7045 * @short Encodes the string into base64 encoding. 7046 * @extra This method wraps native methods when available, and uses a custom 7047 * implementation when not available. It can also handle Unicode 7048 * string encodings. 7049 * 7050 * @example 7051 * 7052 * 'gonna get encoded!'.encodeBase64() -> 'Z29ubmEgZ2V0IGVuY29kZWQh' 7053 * 'http://twitter.com/'.encodeBase64() -> 'aHR0cDovL3R3aXR0ZXIuY29tLw==' 7054 * 7055 ***/ 7056 'encodeBase64': function(str) { 7057 return encodeBase64(str); 7058 }, 7059 7060 /*** 7061 * @method decodeBase64() 7062 * @returns String 7063 * @short Decodes the string from base64 encoding. 7064 * @extra This method wraps native methods when available, and uses a custom 7065 * implementation when not available. It can also handle Unicode string 7066 * encodings. 7067 * 7068 * @example 7069 * 7070 * 'aHR0cDovL3R3aXR0ZXIuY29tLw=='.decodeBase64() -> 'http://twitter.com/' 7071 * 'anVzdCBnb3QgZGVjb2RlZA=='.decodeBase64() -> 'just got decoded!' 7072 * 7073 ***/ 7074 'decodeBase64': function(str) { 7075 return decodeBase64(str); 7076 }, 7077 7078 /*** 7079 * @method forEach([search], [callback]) 7080 * @returns Array 7081 * @short Runs callback [fn] against every character in the string, or every 7082 * every occurence of [search] if it is provided. 7083 * @extra Returns an array of matches. [search] may be either a string or 7084 * regex, and defaults to every character in the string. If [fn] 7085 * returns false at any time it will break out of the loop. 7086 * 7087 * @callback stringEachFn 7088 * 7089 * match The current match. 7090 * i The current index. 7091 * arr An array of all matches. 7092 * 7093 * @example 7094 * 7095 * 'jumpy'.forEach(log) -> ['j','u','m','p','y'] 7096 * 'jumpy'.forEach(/[r-z]/) -> ['u','y'] 7097 * 'jumpy'.forEach(/mp/) -> ['mp'] 7098 * 'jumpy'.forEach(/[r-z]/, function(m) { 7099 * // Called twice: "u", "y" 7100 * }); 7101 * 7102 * @signature forEach(callback) 7103 * @param {string|RegExp} [search] 7104 * @param {stringEachFn} [callback] 7105 * @callbackParam {string} match 7106 * @callbackParam {number} i 7107 * @callbackParam {Array<string>} arr 7108 * 7109 ***/ 7110 'forEach': function(str, search, fn) { 7111 return stringEach(str, search, fn); 7112 }, 7113 7114 /*** 7115 * @method chars([callback]) 7116 * @returns Array 7117 * @short Runs [fn] against each character in the string, and returns an array. 7118 * 7119 * @callback eachCharFn 7120 * 7121 * char The current character. 7122 * i The current index. 7123 * arr An array of all characters. 7124 * 7125 * @example 7126 * 7127 * 'jumpy'.chars() -> ['j','u','m','p','y'] 7128 * 'jumpy'.chars(function(c) { 7129 * // Called 5 times: "j","u","m","p","y" 7130 * }); 7131 * 7132 * @param {eachCharFn} [callback] 7133 * @callbackParam {string} char 7134 * @callbackParam {number} i 7135 * @callbackParam {Array<string>} arr 7136 * 7137 ***/ 7138 'chars': function(str, search, fn) { 7139 return stringEach(str, search, fn); 7140 }, 7141 7142 /*** 7143 * @method words([callback]) 7144 * @returns Array 7145 * @short Runs [fn] against each word in the string, and returns an array. 7146 * @extra A "word" is defined as any sequence of non-whitespace characters. 7147 * 7148 * @callback eachWordFn 7149 * 7150 * word The current word. 7151 * i The current index. 7152 * arr An array of all words. 7153 * 7154 * @example 7155 * 7156 * 'broken wear'.words() -> ['broken','wear'] 7157 * 'broken wear'.words(function(w) { 7158 * // Called twice: "broken", "wear" 7159 * }); 7160 * 7161 * @param {eachWordFn} [callback] 7162 * @callbackParam {string} word 7163 * @callbackParam {number} i 7164 * @callbackParam {Array<string>} arr 7165 * 7166 ***/ 7167 'words': function(str, fn) { 7168 return stringEach(trim(str), /\S+/g, fn); 7169 }, 7170 7171 /*** 7172 * @method lines([callback]) 7173 * @returns Array 7174 * @short Runs [fn] against each line in the string, and returns an array. 7175 * 7176 * @callback eachLineFn 7177 * 7178 * line The current line. 7179 * i The current index. 7180 * arr An array of all lines. 7181 * 7182 * @example 7183 * 7184 * lineText.lines() -> array of lines 7185 * lineText.lines(function(l) { 7186 * // Called once per line 7187 * }); 7188 * 7189 * @param {eachLineFn} [callback] 7190 * @callbackParam {string} line 7191 * @callbackParam {number} i 7192 * @callbackParam {Array<string>} arr 7193 * 7194 ***/ 7195 'lines': function(str, fn) { 7196 return stringEach(trim(str), /^.*$/gm, fn); 7197 }, 7198 7199 /*** 7200 * @method codes([callback]) 7201 * @returns Array 7202 * @short Runs callback [fn] against each character code in the string. 7203 * Returns an array of character codes. 7204 * 7205 * @callback eachCodeFn 7206 * 7207 * code The current character code. 7208 * i The current index. 7209 * str The string being operated on. 7210 * 7211 * @example 7212 * 7213 * 'jumpy'.codes() -> [106,117,109,112,121] 7214 * 'jumpy'.codes(function(c) { 7215 * // Called 5 times: 106, 117, 109, 112, 121 7216 * }); 7217 * 7218 * @param {eachCodeFn} [callback] 7219 * @callbackParam {number} code 7220 * @callbackParam {number} i 7221 * @callbackParam {string} str 7222 * 7223 ***/ 7224 'codes': function(str, fn) { 7225 return stringCodes(str, fn); 7226 }, 7227 7228 /*** 7229 * @method shift(n) 7230 * @returns Array 7231 * @short Shifts each character in the string `n` places in the character map. 7232 * 7233 * @example 7234 * 7235 * 'a'.shift(1) -> 'b' 7236 * 'ã¯'.shift(1) -> 'ã°' 7237 * 7238 * @param {number} n 7239 * 7240 ***/ 7241 'shift': function(str, n) { 7242 var result = ''; 7243 n = n || 0;
7244 stringCodes(str, function(c) { 7245 result += chr(c + n); 7246 }); 7247 return result; 7248 }, 7249 7250 /*** 7251 * @method isBlank() 7252 * @returns Boolean 7253 * @short Returns true if the string has length 0 or contains only whitespace. 7254 * 7255 * @example 7256 * 7257 * ''.isBlank() -> true 7258 * ' '.isBlank() -> true 7259 * 'noway'.isBlank() -> false 7260 * 7261 ***/ 7262 'isBlank': function(str) { 7263 return trim(str).length === 0; 7264 }, 7265 7266 /*** 7267 * @method isEmpty() 7268 * @returns Boolean 7269 * @short Returns true if the string has length 0. 7270 * 7271 * @example 7272 * 7273 * ''.isEmpty() -> true 7274 * 'a'.isBlank() -> false 7275 * ' '.isBlank() -> false 7276 * 7277 ***/ 7278 'isEmpty': function(str) { 7279 return str.length === 0; 7280 }, 7281 7282 /*** 7283 * @method insert(str, [index] = length) 7284 * @returns String 7285 * @short Adds `str` at [index]. Allows negative values. 7286 * 7287 * @example 7288 * 7289 * 'dopamine'.insert('e', 3) -> dopeamine 7290 * 'spelling eror'.insert('r', -3) -> spelling error 7291 * 7292 * @param {string} str 7293 * @param {number} [index] 7294 * 7295 ***/ 7296 'insert': function(str, substr, index) { 7297 index = isUndefined(index) ? str.length : index; 7298 return str.slice(0, index) + substr + str.slice(index); 7299 }, 7300 7301 /*** 7302 * @method remove(f) 7303 * @returns String 7304 * @short Removes the first occurrence of `f` in the string. 7305 * @extra `f` can be a either case-sensitive string or a regex. In either case 7306 * only the first match will be removed. To remove multiple occurrences, 7307 * use `removeAll`. 7308 * 7309 * @example 7310 * 7311 * 'schfifty five'.remove('f') -> 'schifty five' 7312 * 'schfifty five'.remove(/[a-f]/g) -> 'shfifty five' 7313 * 7314 * @param {string|RegExp} f 7315 * 7316 ***/ 7317 'remove': function(str, f) { 7318 return str.replace(f, ''); 7319 }, 7320 7321 /*** 7322 * @method removeAll(f) 7323 * @returns String 7324 * @short Removes any occurences of `f` in the string. 7325 * @extra `f` can be either a case-sensitive string or a regex. In either case 7326 * all matches will be removed. To remove only a single occurence, use 7327 * `remove`. 7328 * 7329 * @example 7330 * 7331 * 'schfifty five'.removeAll('f') -> 'schity ive' 7332 * 'schfifty five'.removeAll(/[a-f]/) -> 'shity iv' 7333 * 7334 * @param {string|RegExp} f 7335 * 7336 ***/ 7337 'removeAll': function(str, f) { 7338 return stringReplaceAll(str, f); 7339 }, 7340 7341 /*** 7342 * @method reverse() 7343 * @returns String 7344 * @short Reverses the string. 7345 * 7346 * @example 7347 * 7348 * 'jumpy'.reverse() -> 'ypmuj' 7349 * 'lucky charms'.reverse() -> 'smrahc ykcul' 7350 * 7351 ***/ 7352 'reverse': function(str) { 7353 return reverseString(str); 7354 }, 7355 7356 /*** 7357 * @method compact() 7358 * @returns String 7359 * @short Compacts whitespace in the string to a single space and trims the ends. 7360 * 7361 * @example 7362 * 7363 * 'too \n much \n space'.compact() -> 'too much space' 7364 * 'enough \n '.compact() -> 'enought' 7365 * 7366 ***/ 7367 'compact': function(str) { 7368 return trim(str).replace(/([\r\n\sã])+/g, function(match, whitespace) { 7369 return whitespace === 'ã' ? whitespace : ' '; 7370 }); 7371 }, 7372 7373 /*** 7374 * @method from([index] = 0) 7375 * @returns String 7376 * @short Returns a section of the string starting from [index]. 7377 * 7378 * @example 7379 * 7380 * 'lucky charms'.from() -> 'lucky charms' 7381 * 'lucky charms'.from(7) -> 'harms' 7382 * 7383 * @param {number} [index] 7384 * 7385 ***/ 7386 'from': function(str, from) { 7387 return str.slice(numberOrIndex(str, from, true)); 7388 }, 7389 7390 /*** 7391 * @method to([index] = end) 7392 * @returns String 7393 * @short Returns a section of the string ending at [index]. 7394 * 7395 * @example 7396 * 7397 * 'lucky charms'.to() -> 'lucky charms' 7398 * 'lucky charms'.to(7) -> 'lucky ch' 7399 * 7400 * @param {number} [index] 7401 * 7402 ***/ 7403 'to': function(str, to) { 7404 if (isUndefined(to)) to = str.length; 7405 return str.slice(0, numberOrIndex(str, to)); 7406 }, 7407 7408 /*** 7409 * @method dasherize() 7410 * @returns String 7411 * @short Converts underscores and camel casing to hypens. 7412 * 7413 * @example 7414 * 7415 * 'a_farewell_to_arms'.dasherize() -> 'a-farewell-to-arms' 7416 * 'capsLock'.dasherize() -> 'caps-lock' 7417 * 7418 ***/ 7419 'dasherize': function(str) { 7420 return stringUnderscore(str).replace(/_/g, '-'); 7421 }, 7422 7423 /*** 7424 * @method underscore() 7425 * @returns String 7426 * @short Converts hyphens and camel casing to underscores. 7427 * 7428 * @example 7429 * 7430 * 'a-farewell-to-arms'.underscore() -> 'a_farewell_to_arms' 7431 * 'capsLock'.underscore() -> 'caps_lock' 7432 * 7433 ***/ 7434 'underscore': function(str) { 7435 return stringUnderscore(str); 7436 }, 7437 7438 /*** 7439 * @method camelize([upper] = true) 7440 * @returns String 7441 * @short Converts underscores and hyphens to camel case.
7442 * @extra If [upper] is true, the string will be UpperCamelCase. If the 7443 * inflections module is included, acronyms can also be defined that 7444 * will be used when camelizing. 7445 * 7446 * @example 7447 * 7448 * 'caps_lock'.camelize() -> 'CapsLock' 7449 * 'moz-border-radius'.camelize() -> 'MozBorderRadius' 7450 * 'moz-border-radius'.camelize(false) -> 'mozBorderRadius' 7451 * 'http-method'.camelize() -> 'HTTPMethod' 7452 * 7453 * @param {boolean} [upper] 7454 * 7455 ***/ 7456 'camelize': function(str, upper) { 7457 return stringCamelize(str, upper); 7458 }, 7459 7460 /*** 7461 * @method spacify() 7462 * @returns String 7463 * @short Converts camelcase, underscores, and hyphens to spaces. 7464 * 7465 * @example 7466 * 7467 * 'camelCase'.spacify() -> 'camel case' 7468 * 'an-ugly-string'.spacify() -> 'an ugly string' 7469 * 'oh-no_youDid-not'.spacify().capitalize(true) -> 'something else' 7470 * 7471 ***/ 7472 'spacify': function(str) { 7473 return stringSpacify(str); 7474 }, 7475 7476 /*** 7477 * @method titleize() 7478 * @returns String 7479 * @short Creates a title version of the string. 7480 * @extra Capitalizes all the words and replaces some characters in the string 7481 * to create a nicer looking title. String#titleize is meant for 7482 * creating pretty output. 7483 * 7484 * @example 7485 * 7486 * 'man from the boondocks'.titleize() -> 'Man from the Boondocks' 7487 * 'x-men: apocalypse'.titleize() -> 'X Men: Apocalypse' 7488 * 'TheManWithoutAPast'.titleize() -> 'The Man Without a Past' 7489 * 'raiders_of_the_lost_ark'.titleize() -> 'Raiders of the Lost Ark' 7490 * 7491 ***/ 7492 'titleize': function(str) { 7493 return stringTitleize(str); 7494 }, 7495 7496 /*** 7497 * @method parameterize() 7498 * @returns String 7499 * @short Replaces special characters in a string so that it may be used as 7500 * part of a pretty URL. 7501 * 7502 * @example 7503 * 7504 * 'hell, no!'.parameterize() -> 'hell-no' 7505 * 7506 ***/ 7507 'parameterize': function(str, separator) { 7508 return stringParameterize(str, separator); 7509 }, 7510 7511 /*** 7512 * @method truncate(length, [from] = 'right', [ellipsis] = '...') 7513 * @returns String 7514 * @short Truncates a string. 7515 * @extra [from] can be `'right'`, `'left'`, or `'middle'`. If the string is 7516 * shorter than `length`, [ellipsis] will not be added. 7517 * 7518 * @example 7519 * 7520 * 'sittin on the dock'.truncate(10) -> 'sittin on ...' 7521 * 'sittin on the dock'.truncate(10, 'left') -> '...n the dock' 7522 * 'sittin on the dock'.truncate(10, 'middle') -> 'sitti... dock' 7523 * 7524 * @param {number} length 7525 * @param {string} [from] 7526 * @param {string} [ellipsis] 7527 * 7528 ***/ 7529 'truncate': function(str, length, from, ellipsis) { 7530 return truncateString(str, length, from, ellipsis); 7531 }, 7532 7533 /*** 7534 * @method truncateOnWord(length, [from] = 'right', [ellipsis] = '...') 7535 * @returns String 7536 * @short Truncates a string without splitting up words. 7537 * @extra [from] can be `'right'`, `'left'`, or `'middle'`. If the string is 7538 * shorter than `length`, [ellipsis] will not be added. A "word" is 7539 * defined as any sequence of non-whitespace characters. 7540 * 7541 * @example 7542 * 7543 * 'here we go'.truncateOnWord(5) -> 'here...' 7544 * 'here we go'.truncateOnWord(5, 'left') -> '...we go' 7545 * 7546 * @param {number} length 7547 * @param {string} [from] 7548 * @param {string} [ellipsis] 7549 * 7550 ***/ 7551 'truncateOnWord': function(str, length, from, ellipsis) { 7552 return truncateString(str, length, from, ellipsis, true); 7553 }, 7554 7555 /*** 7556 * @method pad(num, [padding] = ' ') 7557 * @returns String 7558 * @short Pads the string out with [padding] to be exactly `num` characters. 7559 * 7560 * @example 7561 * 7562 * 'wasabi'.pad(8) -> ' wasabi ' 7563 * 'wasabi'.pad(8, '-') -> '-wasabi-' 7564 * 7565 * @param {number} num 7566 * @param {string} [padding] 7567 * 7568 ***/ 7569 'pad': function(str, num, padding) { 7570 var half, front, back; 7571 num = coercePositiveInteger(num); 7572 half = max(0, num - str.length) / 2; 7573 front = floor(half); 7574 back = ceil(half); 7575 return padString(front, padding) + str + padString(back, padding); 7576 }, 7577 7578 /*** 7579 * @method padLeft(num, [padding] = ' ') 7580 * @returns String 7581 * @short Pads the string out from the left with [padding] to be exactly 7582 * `num` characters. 7583 * 7584 * @example 7585 * 7586 * 'wasabi'.padLeft(8) -> ' wasabi' 7587 * 'wasabi'.padLeft(8, '-') -> '--wasabi' 7588 * 7589 * @param {number} num 7590 * @param {string} [padding] 7591 * 7592 ***/ 7593 'padLeft': function(str, num, padding) { 7594 num = coercePositiveInteger(num); 7595 return padString(max(0, num - str.length), padding) + str; 7596 }, 7597 7598 /*** 7599 * @method padRight(num, [padding] = ' ') 7600 * @returns String 7601 * @short Pads the string out from the right with [padding] to be exactly 7602 * `num` characters. 7603 * 7604 * @example 7605 * 7606 * 'wasabi'.padRight(8) -> 'wasabi ' 7607 * 'wasabi'.padRight(8, '-') -> 'wasabi--' 7608 * 7609 * @param {number} num 7610 * @param {string} [padding] 7611 * 7612 ***/ 7613 'padRight': function(str, num, padding) { 7614 num = coercePositiveInteger(num); 7615 return str + padString(max(0, num - str.length), padding); 7616 }, 7617 7618 /*** 7619 * @method first([n] = 1) 7620 * @returns String 7621 * @short Returns the first [n] characters of the string. 7622 * 7623 * @example 7624 * 7625 * 'lucky charms'.first() -> 'l'
7626 * 'lucky charms'.first(3) -> 'luc' 7627 * 7628 * @param {number} [n] 7629 * 7630 ***/ 7631 'first': function(str, num) { 7632 if (isUndefined(num)) num = 1; 7633 return str.substr(0, num); 7634 }, 7635 7636 /*** 7637 * @method last([n] = 1) 7638 * @returns String 7639 * @short Returns the last [n] characters of the string. 7640 * 7641 * @example 7642 * 7643 * 'lucky charms'.last() -> 's' 7644 * 'lucky charms'.last(3) -> 'rms' 7645 * 7646 * @param {number} [n] 7647 * 7648 ***/ 7649 'last': function(str, num) { 7650 if (isUndefined(num)) num = 1; 7651 var start = str.length - num < 0 ? 0 : str.length - num; 7652 return str.substr(start); 7653 }, 7654 7655 /*** 7656 * @method toNumber([base] = 10) 7657 * @returns Number 7658 * @short Converts the string into a number. 7659 * @extra Any value with a "." fill be converted to a floating point value, 7660 * otherwise an integer. 7661 * 7662 * @example 7663 * 7664 * '153'.toNumber() -> 153 7665 * '12,000'.toNumber() -> 12000 7666 * '10px'.toNumber() -> 10 7667 * 'ff'.toNumber(16) -> 255 7668 * 7669 * @param {number} [base] 7670 * 7671 ***/ 7672 'toNumber': function(str, base) { 7673 return stringToNumber(str, base); 7674 }, 7675 7676 /*** 7677 * @method capitalize([lower] = false, [all] = false) 7678 * @returns String 7679 * @short Capitalizes the first character of the string. 7680 * @extra If [lower] is true, the remainder of the string will be downcased. 7681 * If [all] is true, all words in the string will be capitalized. 7682 * 7683 * @example 7684 * 7685 * 'hello'.capitalize() -> 'Hello' 7686 * 'HELLO'.capitalize(true) -> 'Hello' 7687 * 'hello kitty'.capitalize() -> 'Hello kitty' 7688 * 'hEllO kItTy'.capitalize(true, true) -> 'Hello Kitty' 7689 * 7690 * @param {boolean} [lower] 7691 * @param {boolean} [all] 7692 * 7693 ***/ 7694 'capitalize': function(str, lower, all) { 7695 return stringCapitalize(str, lower, all); 7696 }, 7697 7698 /*** 7699 * @method trimLeft() 7700 * @returns String 7701 * @short Removes leading whitespace from the string. 7702 * @extra Whitespace is defined as line breaks, tabs, and any character in the 7703 * "Space, Separator" Unicode category, conforming to the the ES5 `trim` 7704 * spec. 7705 * 7706 * @example 7707 * 7708 * ' wasabi '.trimLeft() -> 'wasabi ' 7709 * 7710 ***/ 7711 'trimLeft': function(str) { 7712 return str.replace(LEFT_TRIM_REG, ''); 7713 }, 7714 7715 /*** 7716 * @method trimRight() 7717 * @returns String 7718 * @short Removes trailing whitespace from the string. 7719 * @extra Whitespace is defined as line breaks, tabs, and any character in the 7720 * "Space, Separator" Unicode category, conforming to the the ES5 `trim` 7721 * spec. 7722 * 7723 * @example 7724 * 7725 * ' wasabi '.trimRight() -> ' wasabi' 7726 * 7727 ***/ 7728 'trimRight': function(str) { 7729 return str.replace(RIGHT_TRIM_REG, ''); 7730 } 7731 7732 }); 7733 7734 defineInstanceWithArguments(sugarString, { 7735 7736 /*** 7737 * @method replaceAll(f, [str1], [str2], ...) 7738 * @returns String 7739 * @short Replaces all occurences of `f` with arguments passed. 7740 * @extra This method is intended to be a quick way to perform multiple string 7741 * replacements quickly when the replacement token differs depending on 7742 * position. `f` can be either a case-sensitive string or a regex. 7743 * In either case all matches will be replaced. 7744 * 7745 * @example 7746 * 7747 * '-x -y -z'.replaceAll('-', 1, 2, 3) -> '1x 2y 3z' 7748 * 'one and two'.replaceAll(/one|two/, '1st', '2nd') -> '1st and 2nd' 7749 * 7750 * @param {string|RegExp} f 7751 * @param {string} [str1] 7752 * @param {string} [str2] 7753 * 7754 ***/ 7755 'replaceAll': function(str, f, args) { 7756 return stringReplaceAll(str, f, args); 7757 }, 7758 7759 /*** 7760 * @method format(obj1, [obj2], ...) 7761 * @returns String 7762 * @short Replaces `{}` tokens in the string with arguments or properties. 7763 * @extra Tokens support `deep properties`. If a single object is passed, its 7764 * properties can be accessed by keywords such as `{name}`. If multiple 7765 * objects or a non-object are passed, they can be accessed by the 7766 * argument position like `{0}`. Literal braces in the string can be 7767 * escaped by repeating them. 7768 * 7769 * @example 7770 * 7771 * 'Welcome, {name}.'.format({ name: 'Bill' }) -> 'Welcome, Bill.' 7772 * 'You are {0} years old today.'.format(5) -> 'You are 5 years old today.' 7773 * '{0.name} and {1.name}'.format(users) -> logs first two users' names 7774 * '${currencies.usd.balance}'.format(Harry) -> "$500" 7775 * '{{Hello}}'.format('Hello') -> "{Hello}" 7776 * 7777 * @param {any} [obj1] 7778 * @param {any} [obj2] 7779 * 7780 ***/ 7781 'format': function(str, args) { 7782 var arg1 = args[0] && args[0].valueOf(); 7783 // Unwrap if a single object is passed in. 7784 if (args.length === 1 && isObjectType(arg1)) { 7785 args = arg1; 7786 } 7787 return stringFormatMatcher(str, args); 7788 } 7789 7790 });
7791 7792 buildBase64(); 7793 buildEntities(); 7794 7795 /*** 7796 * @module Array 7797 * @description Array manipulation and traversal, alphanumeric sorting and collation. 7798 * 7799 ***/ 7800 7801 var HALF_WIDTH_NINE = 0x39; 7802 var FULL_WIDTH_NINE = 0xff19; 7803 7804 // Undefined array elements in < IE8 will not be visited by concat 7805 // and so will not be copied. This means that non-sparse arrays will 7806 // become sparse, so detect for this here. 7807 var HAS_CONCAT_BUG = !('0' in [].concat(undefined).concat()); 7808 7809 var ARRAY_OPTIONS = { 7810 'sortIgnore': null, 7811 'sortNatural': true, 7812 'sortIgnoreCase': true, 7813 'sortOrder': getSortOrder(), 7814 'sortCollate': collateStrings, 7815 'sortEquivalents': getSortEquivalents() 7816 }; 7817 7818 /*** 7819 * @method getOption(name) 7820 * @returns Mixed 7821 * @accessor 7822 * @short Gets an option used interally by Array. 7823 * @extra Options listed below. Current options are for sorting strings with 7824 * `sortBy`. 7825 * 7826 * @example 7827 * 7828 * Sugar.Array.getOption('sortNatural') 7829 * 7830 * @param {string} name 7831 * 7832 *** 7833 * @method setOption(name, value) 7834 * @accessor 7835 * @short Sets an option used interally by Array. 7836 * @extra Options listed below. Current options are for sorting strings with 7837 * `sortBy`. If `value` is `null`, the default value will be restored. 7838 * 7839 * @options 7840 * 7841 * sortIgnore A regex to ignore when sorting. An example usage of this 7842 * option would be to ignore numbers in a list to instead 7843 * sort by the first text that appears. Default is `null`. 7844 * 7845 * sortIgnoreCase A boolean that ignores case when sorting. 7846 * Default is `true`. 7847 * 7848 * sortNatural A boolean that turns on natural sorting. "Natural" means 7849 * that numerals like "10" will be sorted after "9" instead 7850 * of after "1". Default is `true`. 7851 * 7852 * sortOrder A string of characters to use as the base sort order. The 7853 * default is an order natural to most major world languages. 7854 * 7855 * sortEquivalents A table of equivalent characters used when sorting. The 7856 * default produces a natural sort order for most world 7857 * languages, however can be modified for others. For 7858 * example, setting "ä" and "ö" to `null` in the table would 7859 * produce a Scandanavian sort order. Note that setting this 7860 * option to `null` will restore the default table, but any 7861 * mutations made to that table will persist. 7862 * 7863 * sortCollate The collation function used when sorting strings. The 7864 * default function produces a natural sort order that can 7865 * be customized with the other "sort" options. Overriding 7866 * the function directly here will also override these 7867 * options. 7868 * 7869 * @example 7870 * 7871 * Sugar.Array.setOption('sortIgnore', /^\d+\./) 7872 * Sugar.Array.setOption('sortIgnoreCase', false) 7873 * 7874 * @signature setOption(options) 7875 * @param {ArrayOptions} options 7876 * @param {string} name 7877 * @param {any} value 7878 * @option {RegExp} [sortIgnore] 7879 * @option {boolean} [sortIgnoreCase] 7880 * @option {boolean} [sortNatural] 7881 * @option {string} [sortOrder] 7882 * @option {Object} [sortEquivalents] 7883 * @option {Function} [sortCollate] 7884 * 7885 ***/ 7886 var _arrayOptions = defineOptionsAccessor(sugarArray, ARRAY_OPTIONS); 7887 7888 7889 function setArrayChainableConstructor() { 7890 setChainableConstructor(sugarArray, arrayCreate); 7891 } 7892 7893 function isArrayOrInherited(obj) { 7894 return obj && obj.constructor && isArray(obj.constructor.prototype); 7895 } 7896 7897 function arrayCreate(obj, clone) { 7898 var arr; 7899 if (isArrayOrInherited(obj)) { 7900 arr = clone ? arrayClone(obj) : obj;
7901 } else if (isObjectType(obj) || isString(obj)) { 7902 arr = Array.from(obj); 7903 } else if (isDefined(obj)) { 7904 arr = [obj]; 7905 } 7906 return arr || []; 7907 } 7908 7909 function arrayClone(arr) { 7910 var clone = new Array(arr.length); 7911 forEach(arr, function(el, i) { 7912 clone[i] = el; 7913 }); 7914 return clone; 7915 } 7916 7917 function arrayConcat(arr1, arr2) { 7918 if (HAS_CONCAT_BUG) { 7919 return arraySafeConcat(arr1, arr2); 7920 } 7921 return arr1.concat(arr2); 7922 } 7923 7924 // Avoids issues with [undefined] in < IE9 7925 function arrayWrap(obj) { 7926 var arr = []; 7927 arr.push(obj); 7928 return arr; 7929 } 7930 7931 // Avoids issues with concat in < IE8 7932 function arraySafeConcat(arr, arg) { 7933 var result = arrayClone(arr), len = result.length, arr2; 7934 arr2 = isArray(arg) ? arg : [arg]; 7935 result.length += arr2.length; 7936 forEach(arr2, function(el, i) { 7937 result[len + i] = el; 7938 }); 7939 return result; 7940 } 7941 7942 7943 function arrayAppend(arr, el, index) { 7944 var spliceArgs; 7945 index = +index; 7946 if (isNaN(index)) { 7947 index = arr.length; 7948 } 7949 spliceArgs = [index, 0]; 7950 if (isDefined(el)) { 7951 spliceArgs = spliceArgs.concat(el); 7952 } 7953 arr.splice.apply(arr, spliceArgs); 7954 return arr; 7955 } 7956 7957 function arrayRemove(arr, f) { 7958 var matcher = getMatcher(f), i = 0; 7959 while (i < arr.length) { 7960 if (matcher(arr[i], i, arr)) { 7961 arr.splice(i, 1); 7962 } else { 7963 i++; 7964 } 7965 } 7966 return arr; 7967 } 7968 7969 function arrayExclude(arr, f) { 7970 var result = [], matcher = getMatcher(f); 7971 for (var i = 0; i < arr.length; i++) { 7972 if (!matcher(arr[i], i, arr)) { 7973 result.push(arr[i]); 7974 } 7975 } 7976 return result; 7977 } 7978 7979 function arrayUnique(arr, map) { 7980 var result = [], obj = {}, refs = []; 7981 forEach(arr, function(el, i) { 7982 var transformed = map ? mapWithShortcuts(el, map, arr, [el, i, arr]) : el; 7983 var key = serializeInternal(transformed, refs); 7984 if (!hasOwn(obj, key)) { 7985 result.push(el); 7986 obj[key] = true; 7987 } 7988 }); 7989 return result; 7990 } 7991 7992 function arrayFlatten(arr, level, current) { 7993 var result = []; 7994 level = level || Infinity; 7995 current = current || 0; 7996 forEach(arr, function(el) { 7997 if (isArray(el) && current < level) { 7998 result = result.concat(arrayFlatten(el, level, current + 1)); 7999 } else { 8000 result.push(el); 8001 } 8002 }); 8003 return result; 8004 } 8005 8006 function arrayCompact(arr, all) { 8007 return filter(arr, function(el) { 8008 return el || (!all && el != null && el.valueOf() === el.valueOf()); 8009 }); 8010 } 8011 8012 function arrayShuffle(arr) { 8013 arr = arrayClone(arr); 8014 var i = arr.length, j, x; 8015 while (i) { 8016 j = (Math.random() * i) | 0; 8017 x = arr[--i]; 8018 arr[i] = arr[j]; 8019 arr[j] = x; 8020 } 8021 return arr; 8022 } 8023 8024 function arrayGroupBy(arr, map, fn) { 8025 var result = {}, key; 8026 forEach(arr, function(el, i) { 8027 key = mapWithShortcuts(el, map, arr, [el, i, arr]); 8028 if (!hasOwn(result, key)) { 8029 result[key] = []; 8030 } 8031 result[key].push(el); 8032 }); 8033 if (fn) { 8034 forEachProperty(result, fn); 8035 } 8036 return result; 8037 } 8038 8039 function arrayIntersectOrSubtract(arr1, arr2, subtract) { 8040 var result = [], obj = {}, refs = []; 8041 if (!isArray(arr2)) { 8042 arr2 = arrayWrap(arr2); 8043 } 8044 forEach(arr2, function(el) { 8045 obj[serializeInternal(el, refs)] = true; 8046 }); 8047 forEach(arr1, function(el) { 8048 var key = serializeInternal(el, refs); 8049 if (hasOwn(obj, key) !== subtract) { 8050 delete obj[key]; 8051 result.push(el); 8052 } 8053 }); 8054 return result; 8055 } 8056 8057 // Collation helpers 8058 8059 function compareValue(aVal, bVal) { 8060 var cmp, i, collate; 8061 if (isString(aVal) && isString(bVal)) { 8062 collate = _arrayOptions('sortCollate'); 8063 return collate(aVal, bVal); 8064 } else if (isArray(aVal) && isArray(bVal)) { 8065 if (aVal.length < bVal.length) { 8066 return -1; 8067 } else if (aVal.length > bVal.length) { 8068 return 1; 8069 } else { 8070 for (i = 0; i < aVal.length; i++) { 8071 cmp = compareValue(aVal[i], bVal[i]); 8072 if (cmp !== 0) { 8073 return cmp; 8074 } 8075 } 8076 return 0; 8077 } 8078 } 8079 return aVal < bVal ? -1 : aVal > bVal ? 1 : 0; 8080 } 8081 8082 function codeIsNumeral(code) { 8083 return (code >= HALF_WIDTH_ZERO && code <= HALF_WIDTH_NINE) || 8084 (code >= FULL_WIDTH_ZERO && code <= FULL_WIDTH_NINE); 8085 } 8086 8087 function collateStrings(a, b) { 8088 var aValue, bValue, aChar, bChar, aEquiv, bEquiv, index = 0, tiebreaker = 0; 8089 8090 var sortOrder = _arrayOptions('sortOrder'); 8091 var sortIgnore = _arrayOptions('sortIgnore'); 8092 var sortNatural = _arrayOptions('sortNatural'); 8093 var sortIgnoreCase = _arrayOptions('sortIgnoreCase'); 8094 var sortEquivalents = _arrayOptions('sortEquivalents'); 8095 8096 a = getCollationReadyString(a, sortIgnore, sortIgnoreCase); 8097 b = getCollationReadyString(b, sortIgnore, sortIgnoreCase); 8098 8099 do { 8100 8101 aChar = getCollationCharacter(a, index, sortEquivalents); 8102 bChar = getCollationCharacter(b, index, sortEquivalents); 8103 aValue = getSortOrderIndex(aChar, sortOrder); 8104 bValue = getSortOrderIndex(bChar, sortOrder); 8105 8106 if (aValue === -1 || bValue === -1) { 8107 aValue = a.charCodeAt(index) || null; 8108 bValue = b.charCodeAt(index) || null; 8109 if (sortNatural && codeIsNumeral(aValue) && codeIsNumeral(bValue)) { 8110 aValue = stringToNumber(a.slice(index)); 8111 bValue = stringToNumber(b.slice(index)); 8112 } 8113 } else { 8114 aEquiv = aChar !== a.charAt(index); 8115 bEquiv = bChar !== b.charAt(index); 8116 if (aEquiv !== bEquiv && tiebreaker === 0) { 8117 tiebreaker = aEquiv - bEquiv; 8118 } 8119 } 8120 index += 1;
8121 } while (aValue != null && bValue != null && aValue === bValue); 8122 if (aValue === bValue) return tiebreaker; 8123 return aValue - bValue; 8124 } 8125 8126 function getCollationReadyString(str, sortIgnore, sortIgnoreCase) { 8127 if (!isString(str)) str = String(str); 8128 if (sortIgnoreCase) { 8129 str = str.toLowerCase(); 8130 } 8131 if (sortIgnore) { 8132 str = str.replace(sortIgnore, ''); 8133 } 8134 return str; 8135 } 8136 8137 function getCollationCharacter(str, index, sortEquivalents) { 8138 var chr = str.charAt(index); 8139 return getOwn(sortEquivalents, chr) || chr; 8140 } 8141 8142 function getSortOrderIndex(chr, sortOrder) { 8143 if (!chr) { 8144 return null; 8145 } else { 8146 return sortOrder.indexOf(chr); 8147 } 8148 } 8149 8150 function getSortOrder() { 8151 var order = 'AÃÃÃÃÄBCÄÄÃDÄÃEÃÃÄÃÃÄFGÄHıIÃÃİÃÃJKLÅMNÅÅÃOÃÃÃPQRÅSÅÅ ÅTŤUÃÃÅ®ÃÃVWXYÃZŹŻŽÃÃÅÃÃà ÃÃ'; 8152 return map(order.split(''), function(str) { 8153 return str + str.toLowerCase(); 8154 }).join(''); 8155 } 8156 8157 function getSortEquivalents() { 8158 var equivalents = {}; 8159 forEach(spaceSplit('AÃÃÃÃà Cà EÃÃÃà IÃÃİÃà OÃÃÃÃà Sà UÃÃÃÃ'), function(set) { 8160 var first = set.charAt(0);
8161 forEach(set.slice(1).split(''), function(chr) { 8162 equivalents[chr] = first; 8163 equivalents[chr.toLowerCase()] = first.toLowerCase(); 8164 }); 8165 }); 8166 return equivalents; 8167 } 8168 8169 defineStatic(sugarArray, { 8170 8171 /*** 8172 * 8173 * @method create([obj], [clone] = false) 8174 * @returns Array 8175 * @static 8176 * @short Creates an array from an unknown object. 8177 * @extra This method is similar to native `Array.from` but is faster when 8178 * `obj` is already an array. When [clone] is true, the array will be 8179 * shallow cloned. Additionally, it will not fail on `undefined`, 8180 * `null`, or numbers, producing an empty array in the case of 8181 * `undefined` and wrapping `obj` otherwise. 8182 * 8183 * @example 8184 * 8185 * Array.create() -> [] 8186 * Array.create(8) -> [8] 8187 * Array.create('abc') -> ['a','b','c'] 8188 * Array.create([1,2,3]) -> [1, 2, 3] 8189 * Array.create(undefined) -> [] 8190 * 8191 * @param {number|ArrayLike<T>} [obj] 8192 * @param {boolean} [clone] 8193 * 8194 ***/ 8195 'create': function(obj, clone) { 8196 return arrayCreate(obj, clone); 8197 }, 8198 8199 /*** 8200 * 8201 * @method construct(n, map) 8202 * @returns Array 8203 * @static 8204 * @short Constructs an array of `n` length from the values of `map`. 8205 * @extra This function is essentially a shortcut for using `Array.from` with 8206 * `new Array(n)`. 8207 * 8208 * @callback indexMapFn 8209 * 8210 * i The index of the current iteration. 8211 * 8212 * @example 8213 * 8214 * Array.construct(4, function(i) { 8215 * return i * i; 8216 * }); -> [0, 1, 4] 8217 * 8218 * @param {number} n 8219 * @param {indexMapFn} map 8220 * @callbackParam {number} i 8221 * @callbackReturns {any} indexMapFn 8222 * 8223 ***/ 8224 'construct': function(n, fn) { 8225 n = coercePositiveInteger(n); 8226 return Array.from(new Array(n), function(el, i) { 8227 return fn && fn(i); 8228 }); 8229 } 8230 8231 }); 8232 8233 defineInstance(sugarArray, { 8234 8235 /*** 8236 * @method isEmpty() 8237 * @returns Boolean 8238 * @short Returns true if the array has a length of zero. 8239 * 8240 * @example 8241 * 8242 * [].isEmpty() -> true 8243 * ['a'].isEmpty() -> false 8244 * 8245 ***/ 8246 'isEmpty': function(arr) { 8247 return arr.length === 0; 8248 }, 8249 8250 /*** 8251 * @method isEqual(arr) 8252 * @returns Boolean 8253 * @short Returns true if the array is equal to `arr`. 8254 * @extra Objects in the array are considered equal if they are not obserably 8255 * distinguishable. This method is an instance alias for 8256 * `Object.isEqual()`. 8257 * 8258 * @example 8259 * 8260 * ['a','b'].isEqual(['a','b']) -> true 8261 * ['a','b'].isEqual(['a','c']) -> false 8262 * [{a:'a'}].isEqual([{a:'a'}]) -> true 8263 * [5].isEqual([Object(5)]) -> false 8264 * 8265 * @param {Array} arr 8266 * 8267 ***/ 8268 'isEqual': function(a, b) { 8269 return isEqual(a, b); 8270 }, 8271 8272 /*** 8273 * @method clone() 8274 * @returns Array 8275 * @short Creates a shallow clone of the array. 8276 * 8277 * @example 8278 * 8279 * [1,2,3].clone() -> [1,2,3] 8280 * 8281 ***/ 8282 'clone': function(arr) { 8283 return arrayClone(arr); 8284 }, 8285 8286 /*** 8287 * @method at(index, [loop] = false) 8288 * @returns ArrayElement 8289 * @short Gets the element(s) at `index`. 8290 * @extra When [loop] is true, overshooting the end of the array will begin 8291 * counting from the other end. `index` may be negative. If `index` is 8292 * an array, multiple elements will be returned. 8293 * 8294 * @example 8295 * 8296 * [1,2,3].at(0) -> 1 8297 * [1,2,3].at(2) -> 3 8298 * [1,2,3].at(4) -> undefined 8299 * [1,2,3].at(4, true) -> 2 8300 * [1,2,3].at(-1) -> 3 8301 * [1,2,3].at([0, 1]) -> [1, 2] 8302 * 8303 * @param {number|number[]} index 8304 * @param {boolean} [loop] 8305 * 8306 ***/ 8307 'at': function(arr, index, loop) { 8308 return getEntriesForIndexes(arr, index, loop); 8309 }, 8310 8311 /*** 8312 * @method add(item, [index]) 8313 * @returns Array 8314 * @short Adds `item` to the array and returns the result as a new array. 8315 * @extra If `item` is also an array, it will be concatenated instead of 8316 * inserted. [index] will control where `item` is added. Use `append` 8317 * to modify the original array. 8318 * 8319 * @example 8320 * 8321 * [1,2,3,4].add(5) -> [1,2,3,4,5] 8322 * [1,2,3,4].add(8, 1) -> [1,8,2,3,4] 8323 * [1,2,3,4].add([5,6,7]) -> [1,2,3,4,5,6,7] 8324 * 8325 * @param {ArrayElement|Array} item 8326 * @param {number} [index] 8327 * 8328 ***/ 8329 'add': function(arr, item, index) { 8330 return arrayAppend(arrayClone(arr), item, index); 8331 }, 8332 8333 /*** 8334 * @method subtract(item) 8335 * @returns Array 8336 * @short Subtracts `item` from the array and returns the result as a new array. 8337 * @extra If `item` is also an array, all elements in it will be removed. In 8338 * addition to primitives, this method will also deep-check objects for 8339 * equality. 8340 * 8341 * @example 8342 * 8343 * [1,3,5].subtract([5,7,9]) -> [1,3] 8344 * ['a','b'].subtract(['b','c']) -> ['a'] 8345 * [1,2,3].subtract(2) -> [1,3] 8346 * 8347 * @param {ArrayElement|Array} item 8348 * 8349 ***/ 8350 'subtract': function(arr, item) { 8351 return arrayIntersectOrSubtract(arr, item, true); 8352 }, 8353 8354 /*** 8355 * @method append(item, [index]) 8356 * @returns Array 8357 * @short Appends `item` to the array. 8358 * @extra If `item` is also an array, it will be concatenated instead of 8359 * inserted. This method modifies the array! Use `add` to create a new 8360 * array. Additionally, `insert` is provided as an alias that reads 8361 * better when using an index. 8362 * 8363 * @example 8364 * 8365 * [1,2,3,4].append(5) -> [1,2,3,4,5] 8366 * [1,2,3,4].append([5,6,7]) -> [1,2,3,4,5,6,7] 8367 * [1,2,3,4].append(8, 1) -> [1,8,2,3,4] 8368 * 8369 * @param {ArrayElement|Array} item 8370 * @param {number} index 8371 * 8372 ***/ 8373 'append': function(arr, item, index) { 8374 return arrayAppend(arr, item, index); 8375 }, 8376 8377 /*** 8378 * @method removeAt(start, [end]) 8379 * @returns Array 8380 * @short Removes element at `start`. If [end] is specified, removes the range 8381 * between `start` and [end]. This method will modify the array! 8382 * 8383 * @example 8384 * 8385 * ['a','b','c'].removeAt(0) -> ['b','c'] 8386 * [1,2,3,4].removeAt(1, 2) -> [1, 4] 8387 * 8388 * @param {number} start 8389 * @param {number} [end] 8390 * 8391 ***/ 8392 'removeAt': function(arr, start, end) { 8393 if (isUndefined(start)) return arr; 8394 if (isUndefined(end)) end = start;
8395 arr.splice(start, end - start + 1); 8396 return arr; 8397 }, 8398 8399 /*** 8400 * @method unique([map]) 8401 * @returns Array 8402 * @short Removes all duplicate elements in the array. 8403 * @extra [map] may be a function returning the value to be uniqued or a 8404 * string acting as a shortcut. This is most commonly used when you 8405 * only need to check a single field that can ensure the object's 8406 * uniqueness (such as an `id` field). If [map] is not passed, then 8407 * objects will be deep checked for equality. Supports 8408 * `deep properties`. 8409 * 8410 * @callback mapFn 8411 * 8412 * el The element of the current iteration. 8413 * i The index of the current iteration. 8414 * arr A reference to the array. 8415 * 8416 * @example 8417 * 8418 * [1,2,2,3].unique() -> [1,2,3] 8419 * [{a:'a'},{a:'a'}].unique() -> [{a:'a'}] 8420 * 8421 * users.unique(function(user) { 8422 * return user.id; 8423 * }); -> users array uniqued by id 8424 * 8425 * users.unique('id') -> users array uniqued by id 8426 * 8427 * @param {string|mapFn} map 8428 * @callbackParam {ArrayElement} el 8429 * @callbackParam {number} i 8430 * @callbackParam {Array} arr 8431 * @callbackReturns {NewArrayElement} mapFn 8432 * 8433 ***/ 8434 'unique': function(arr, map) { 8435 return arrayUnique(arr, map); 8436 }, 8437 8438 /*** 8439 * @method flatten([limit] = Infinity) 8440 * @returns Array 8441 * @short Returns a flattened, one-dimensional copy of the array. 8442 * @extra You can optionally specify a [limit], which will only flatten to 8443 * that depth. 8444 * 8445 * @example 8446 * 8447 * [[1], 2, [3]].flatten() -> [1,2,3] 8448 * [[1],[],2,3].flatten() -> [1,2,3] 8449 * 8450 * @param {number} [limit] 8451 * 8452 ***/ 8453 'flatten': function(arr, limit) { 8454 return arrayFlatten(arr, limit); 8455 }, 8456 8457 /*** 8458 * @method first([num] = 1) 8459 * @returns Mixed 8460 * @short Returns the first element(s) in the array. 8461 * @extra When `num` is passed, returns the first `num` elements in the array. 8462 * 8463 * @example 8464 * 8465 * [1,2,3].first() -> 1 8466 * [1,2,3].first(2) -> [1,2] 8467 * 8468 * @param {number} [num] 8469 * 8470 ***/ 8471 'first': function(arr, num) { 8472 if (isUndefined(num)) return arr[0]; 8473 if (num < 0) num = 0; 8474 return arr.slice(0, num); 8475 }, 8476 8477 /*** 8478 * @method last([num] = 1) 8479 * @returns Mixed 8480 * @short Returns the last element(s) in the array. 8481 * @extra When `num` is passed, returns the last `num` elements in the array. 8482 * 8483 * @example 8484 * 8485 * [1,2,3].last() -> 3 8486 * [1,2,3].last(2) -> [2,3] 8487 * 8488 * @param {number} [num] 8489 * 8490 ***/ 8491 'last': function(arr, num) { 8492 if (isUndefined(num)) return arr[arr.length - 1]; 8493 var start = arr.length - num < 0 ? 0 : arr.length - num; 8494 return arr.slice(start); 8495 }, 8496 8497 /*** 8498 * @method from(index) 8499 * @returns Array 8500 * @short Returns a slice of the array from `index`. 8501 * 8502 * @example 8503 * 8504 * ['a','b','c'].from(1) -> ['b','c'] 8505 * ['a','b','c'].from(2) -> ['c'] 8506 * 8507 * @param {number} [index] 8508 * 8509 ***/ 8510 'from': function(arr, num) { 8511 return arr.slice(num); 8512 }, 8513 8514 /*** 8515 * @method to(index) 8516 * @returns Array 8517 * @short Returns a slice of the array up to `index`. 8518 * 8519 * @example 8520 * 8521 * ['a','b','c'].to(1) -> ['a'] 8522 * ['a','b','c'].to(2) -> ['a','b'] 8523 * 8524 * @param {number} [index] 8525 * 8526 ***/ 8527 'to': function(arr, num) { 8528 if (isUndefined(num)) num = arr.length; 8529 return arr.slice(0, num); 8530 }, 8531 8532 /*** 8533 * @method compact([all] = false) 8534 * @returns Array 8535 * @short Removes all instances of `undefined`, `null`, and `NaN` from the array. 8536 * @extra If [all] is `true`, all "falsy" elements will be removed. This 8537 * includes empty strings, `0`, and `false`. 8538 * 8539 * @example 8540 * 8541 * [1,null,2,undefined,3].compact() -> [1,2,3] 8542 * [1,'',2,false,3].compact() -> [1,'',2,false,3] 8543 * [1,'',2,false,3].compact(true) -> [1,2,3] 8544 * [null, [null, 'bye']].compact() -> ['hi', [null, 'bye']] 8545 * 8546 * @param {boolean} [all] 8547 * 8548 ***/ 8549 'compact': function(arr, all) { 8550 return arrayCompact(arr, all); 8551 }, 8552 8553 /*** 8554 * @method groupBy(map, [fn]) 8555 * @returns Object 8556 * @short Groups the array by `map`. 8557 * @extra Will return an object whose keys are the mapped from `map`, which 8558 * may be a mapping function, or a string acting as a shortcut. `map` 8559 * supports `deep properties`. Optionally calls [fn] for each group. 8560 * 8561 * @callback mapFn 8562 * 8563 * el The element of the current iteration. 8564 * i The index of the current iteration. 8565 * arr A reference to the array. 8566 * 8567 * @callback groupFn 8568 * 8569 * arr The current group as an array. 8570 * key The unique key of the current group. 8571 * obj A reference to the object. 8572 * 8573 * @example 8574 * 8575 * ['a','aa','aaa'].groupBy('length') -> { 1: ['a'], 2: ['aa'], 3: ['aaa'] } 8576 * 8577 * users.groupBy(function(n) { 8578 * return n.age; 8579 * }); -> users array grouped by age 8580 * 8581 * users.groupBy('age', function(age, users) { 8582 * // iterates each grouping 8583 * }); 8584 * 8585 * @param {string|mapFn} map 8586 * @param {groupFn} fn 8587 * @callbackParam {ArrayElement} el 8588 * @callbackParam {number} i 8589 * @callbackParam {Array} arr 8590 * @callbackParam {string} key 8591 * @callbackParam {Object} obj 8592 * @callbackReturns {NewArrayElement} mapFn 8593 * 8594 ***/ 8595 'groupBy': function(arr, map, fn) { 8596 return arrayGroupBy(arr, map, fn); 8597 }, 8598 8599 /*** 8600 * @method inGroups(num, [padding]) 8601 * @returns Array 8602 * @short Groups the array into `num` arrays. 8603 * @extra If specified, [padding] will be added to the last array to be of 8604 * equal length. 8605 * 8606 * @example 8607 * 8608 * [1,2,3,4,5,6,7].inGroups(3) -> [[1,2,3],[4,5,6],[7]] 8609 * [1,2,3,4,5,6,7].inGroups(3, 0) -> [[1,2,3],[4,5,6],[7,0,0]] 8610 * 8611 * @param {number} num 8612 * @param {any} [padding] 8613 * 8614 ***/ 8615 'inGroups': function(arr, num, padding) { 8616 var pad = isDefined(padding); 8617 var result = new Array(num); 8618 var divisor = ceil(arr.length / num); 8619 simpleRepeat(num, function(i) { 8620 var index = i * divisor; 8621 var group = arr.slice(index, index + divisor); 8622 if (pad && group.length < divisor) { 8623 simpleRepeat(divisor - group.length, function() { 8624 group.push(padding); 8625 }); 8626 } 8627 result[i] = group; 8628 }); 8629 return result; 8630 }, 8631 8632 /*** 8633 * @method inGroupsOf(num, [padding] = null) 8634 * @returns Array 8635 * @short Groups the array into arrays of `num` elements each. 8636 * @extra [padding] will be added to the last array to be of equal length. 8637 * 8638 * @example 8639 * 8640 * [1,2,3,4,5,6,7].inGroupsOf(4) -> [ [1,2,3,4], [5,6,7] ] 8641 * [1,2,3,4,5,6,7].inGroupsOf(4, 0) -> [ [1,2,3,4], [5,6,7,0] ] 8642 * 8643 * @param {number} num 8644 * @param {any} [padding] 8645 * 8646 ***/ 8647 'inGroupsOf': function(arr, num, padding) { 8648 var result = [], len = arr.length, group; 8649 if (len === 0 || num === 0) return arr; 8650 if (isUndefined(num)) num = 1; 8651 if (isUndefined(padding)) padding = null; 8652 simpleRepeat(ceil(len / num), function(i) { 8653 group = arr.slice(num * i, num * i + num); 8654 while (group.length < num) { 8655 group.push(padding); 8656 } 8657 result.push(group); 8658 }); 8659 return result; 8660 }, 8661 8662 /*** 8663 * @method shuffle() 8664 * @returns Array 8665 * @short Returns a copy of the array with the elements randomized. 8666 * @extra Uses Fisher-Yates algorithm. 8667 * 8668 * @example 8669 * 8670 * [1,2,3,4].shuffle() -> [?,?,?,?] 8671 * 8672 ***/ 8673 'shuffle': function(arr) { 8674 return arrayShuffle(arr); 8675 }, 8676 8677 /*** 8678 * @method sample([num] = 1, [remove] = false) 8679 * @returns Mixed 8680 * @short Returns a random element from the array. 8681 * @extra If [num] is passed, will return an array of [num] elements. If 8682 * [remove] is true, sampled elements will also be removed from the 8683 * array. [remove] can also be passed in place of [num]. 8684 * 8685 * @example 8686 * 8687 * [1,2,3,4,5].sample() -> // Random element 8688 * [1,2,3,4,5].sample(1) -> // Array of 1 random element 8689 * [1,2,3,4,5].sample(3) -> // Array of 3 random elements 8690 * 8691 * @param {number} [num] 8692 * @param {boolean} [remove] 8693 * 8694 ***/
8695 'sample': function(arr, arg1, arg2) { 8696 var result = [], num, remove, single; 8697 if (isBoolean(arg1)) { 8698 remove = arg1; 8699 } else { 8700 num = arg1; 8701 remove = arg2; 8702 } 8703 if (isUndefined(num)) { 8704 num = 1; 8705 single = true; 8706 } 8707 if (!remove) { 8708 arr = arrayClone(arr); 8709 } 8710 num = min(num, arr.length); 8711 for (var i = 0, index; i < num; i++) { 8712 index = trunc(Math.random() * arr.length); 8713 result.push(arr[index]); 8714 arr.splice(index, 1); 8715 } 8716 return single ? result[0] : result; 8717 }, 8718 8719 /*** 8720 * @method sortBy([map], [desc] = false) 8721 * @returns Array 8722 * @short Enhanced sorting function that will sort the array by `map`. 8723 * @extra `map` may be a function, a string acting as a shortcut, an array 8724 * (comparison by multiple values), or blank (direct comparison of 8725 * array values). `map` supports `deep properties`. [desc] will sort 8726 * the array in descending order. When the field being sorted on is 8727 * a string, the resulting order will be determined by an internal 8728 * collation algorithm that is optimized for major Western languages, 8729 * but can be customized using sorting accessors such as `sortIgnore`. 8730 * This method will modify the array! 8731 * 8732 * @callback sortMapFn 8733 * 8734 * el An array element. 8735 * 8736 * @example 8737 * 8738 * ['world','a','new'].sortBy('length') -> ['a','new','world'] 8739 * ['world','a','new'].sortBy('length', true) -> ['world','new','a'] 8740 * users.sortBy(function(n) { 8741 * return n.age; 8742 * }); -> users array sorted by age 8743 * users.sortBy('age') -> users array sorted by age 8744 * 8745 * @param {string|sortMapFn} [map] 8746 * @param {boolean} [desc] 8747 * @callbackParam {ArrayElement} el 8748 * @callbackReturns {NewArrayElement} sortMapFn 8749 * 8750 ***/ 8751 'sortBy': function(arr, map, desc) { 8752 arr.sort(function(a, b) { 8753 var aProperty = mapWithShortcuts(a, map, arr, [a]); 8754 var bProperty = mapWithShortcuts(b, map, arr, [b]); 8755 return compareValue(aProperty, bProperty) * (desc ? -1 : 1); 8756 }); 8757 return arr; 8758 }, 8759 8760 /*** 8761 * @method remove(search) 8762 * @returns Array 8763 * @short Removes any element in the array that matches `search`. 8764 * @extra This method will modify the array! Use `exclude` for a 8765 * non-destructive alias. This method implements `enhanced matching`. 8766 * 8767 * @callback searchFn 8768 * 8769 * el The element of the current iteration. 8770 * i The index of the current iteration. 8771 * arr A reference to the array. 8772 * 8773 * @example 8774 * 8775 * [1,2,3].remove(3) -> [1,2] 8776 * ['a','b','c'].remove(/b/) -> ['a','c'] 8777 * [{a:1},{b:2}].remove(function(n) { 8778 * return n['a'] == 1; 8779 * }); -> [{b:2}] 8780 * 8781 * @param {ArrayElement|searchFn} search 8782 * @callbackParam {ArrayElement} el 8783 * @callbackParam {number} i 8784 * @callbackParam {Array} arr 8785 * @callbackReturns {boolean} searchFn 8786 * 8787 ***/ 8788 'remove': function(arr, f) { 8789 return arrayRemove(arr, f); 8790 }, 8791 8792 /*** 8793 * @method exclude(search) 8794 * @returns Array 8795 * @short Returns a new array with every element that does not match `search`. 8796 * @extra This method can be thought of as the inverse of `Array#filter`. It 8797 * will not modify the original array, Use `remove` to modify the 8798 * array in place. Implements `enhanced matching`. 8799 * 8800 * @callback searchFn 8801 * 8802 * el The element of the current iteration. 8803 * i The index of the current iteration. 8804 * arr A reference to the array. 8805 * 8806 * @example 8807 * 8808 * [1,2,3].exclude(3) -> [1,2] 8809 * ['a','b','c'].exclude(/b/) -> ['a','c'] 8810 * [{a:1},{b:2}].exclude(function(n) { 8811 * return n['a'] == 1; 8812 * }); -> [{b:2}] 8813 * 8814 * @param {ArrayElement|searchFn} search 8815 * @callbackParam {ArrayElement} el 8816 * @callbackParam {number} i 8817 * @callbackParam {Array} arr 8818 * @callbackReturns {boolean} searchFn 8819 * 8820 ***/ 8821 'exclude': function(arr, f) { 8822 return arrayExclude(arr, f); 8823 }, 8824 8825 /*** 8826 * @method union(arr) 8827 * @returns Array 8828 * @short Returns a new array containing elements in both arrays with 8829 * duplicates removed. 8830 * @extra In addition to primitives, this method will also deep-check objects 8831 * for equality. 8832 * 8833 * @example 8834 * 8835 * [1,3,5].union([5,7,9]) -> [1,3,5,7,9] 8836 * ['a','b'].union(['b','c']) -> ['a','b','c'] 8837 * 8838 * @param {Array} arr 8839 * 8840 ***/ 8841 'union': function(arr1, arr2) { 8842 return arrayUnique(arrayConcat(arr1, arr2)); 8843 }, 8844 8845 /*** 8846 * @method intersect(arr) 8847 * @returns Array 8848 * @short Returns a new array containing any elements that both arrays have in 8849 * common. 8850 * @extra In addition to primitives, this method will also deep-check objects 8851 * for equality. 8852 * 8853 * @example 8854 * 8855 * [1,3,5].intersect([5,7,9]) -> [5] 8856 * ['a','b'].intersect(['b','c']) -> ['b'] 8857 * 8858 * @param {Array} arr 8859 * 8860 ***/ 8861 'intersect': function(arr1, arr2) { 8862 return arrayIntersectOrSubtract(arr1, arr2, false); 8863 } 8864 8865 });
8866 8867 defineInstanceWithArguments(sugarArray, { 8868 8869 /*** 8870 * @method zip([arr1], [arr2], ...) 8871 * @returns Array 8872 * @short Merges multiple arrays together. 8873 * @extra This method "zips up" smaller arrays into one large whose elements 8874 * are "all elements at index 0", "all elements at index 1", etc. 8875 * Useful when you have associated data that is split over separated 8876 * arrays. If the arrays passed have more elements than the original 8877 * array, they will be discarded. If they have fewer elements, the 8878 * missing elements will filled with `null`. 8879 * 8880 * @example 8881 * 8882 * [1,2,3].zip([4,5,6]) -> [[1,2], [3,4], [5,6]] 8883 * 8884 * @param {Array} arr1 8885 * @param {Array} arr2 8886 * 8887 ***/ 8888 'zip': function(arr, args) { 8889 return map(arr, function(el, i) { 8890 return [el].concat(map(args, function(k) { 8891 return (i in k) ? k[i] : null; 8892 })); 8893 }); 8894 } 8895 8896 }); 8897 8898 /*** 8899 * @method insert(item, [index]) 8900 * @returns Array 8901 * @short Appends `item` to the array at [index]. 8902 * @extra This method is simply a more readable alias for `append` when passing 8903 * an index. If `el` is an array it will be joined. This method modifies 8904 * the array! Use `add` as a non-destructive alias. 8905 * 8906 * @example 8907 * 8908 * [1,3,4,5].insert(2, 1) -> [1,2,3,4,5] 8909 * [1,4,5,6].insert([2,3], 1) -> [1,2,3,4,5,6] 8910 * 8911 * @param {ArrayElement|Array} item 8912 * @param {number} [index] 8913 * 8914 ***/ 8915 alias(sugarArray, 'insert', 'append'); 8916 8917 setArrayChainableConstructor(); 8918 8919 /*** 8920 * @module Object 8921 * @description Object creation, manipulation, comparison, type checking, and more. 8922 * 8923 * Much thanks to kangax for his informative aricle about how problems with 8924 * instanceof and constructor: http://bit.ly/1Qds27W 8925 * 8926 ***/ 8927 8928 // Matches bracket-style query strings like user[name] 8929 var DEEP_QUERY_STRING_REG = /^(.+?)(\[.*\])$/; 8930 8931 // Matches any character not allowed in a decimal number. 8932 var NON_DECIMAL_REG = /[^\d.-]/; 8933 8934 // Native methods for merging by descriptor when available. 8935 var getOwnPropertyNames = Object.getOwnPropertyNames; 8936 var getOwnPropertySymbols = Object.getOwnPropertySymbols; 8937 var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; 8938 8939 // Basic Helpers 8940 8941 function isArguments(obj, className) { 8942 className = className || classToString(obj); 8943 // .callee exists on Arguments objects in < IE8 8944 return hasProperty(obj, 'length') && (className === '[object Arguments]' || !!obj.callee); 8945 } 8946 8947 // Query Strings | Creating 8948 8949 function toQueryStringWithOptions(obj, opts) { 8950 opts = opts || {}; 8951 if (isUndefined(opts.separator)) { 8952 opts.separator = '_'; 8953 } 8954 return toQueryString(obj, opts.deep, opts.transform, opts.prefix || '', opts.separator); 8955 } 8956 8957 function toQueryString(obj, deep, transform, prefix, separator) { 8958 if (isArray(obj)) { 8959 return collectArrayAsQueryString(obj, deep, transform, prefix, separator); 8960 } else if (isObjectType(obj) && obj.toString === internalToString) { 8961 return collectObjectAsQueryString(obj, deep, transform, prefix, separator); 8962 } else if (prefix) { 8963 return getURIComponentValue(obj, prefix, transform); 8964 } 8965 return ''; 8966 } 8967 8968 function collectArrayAsQueryString(arr, deep, transform, prefix, separator) { 8969 var el, qc, key, result = []; 8970 // Intentionally treating sparse arrays as dense here by avoiding map, 8971 // otherwise indexes will shift during the process of serialization. 8972 for (var i = 0, len = arr.length; i < len; i++) { 8973 el = arr[i]; 8974 key = prefix + (prefix && deep ? '[]' : ''); 8975 if (!key && !isObjectType(el)) { 8976 // If there is no key, then the values of the array should be
8977 // considered as null keys, so use them instead; 8978 qc = sanitizeURIComponent(el); 8979 } else { 8980 qc = toQueryString(el, deep, transform, key, separator); 8981 } 8982 result.push(qc); 8983 } 8984 return result.join('&'); 8985 } 8986 8987 function collectObjectAsQueryString(obj, deep, transform, prefix, separator) { 8988 var result = []; 8989 forEachProperty(obj, function(val, key) { 8990 var fullKey; 8991 if (prefix && deep) { 8992 fullKey = prefix + '[' + key + ']'; 8993 } else if (prefix) { 8994 fullKey = prefix + separator + key; 8995 } else { 8996 fullKey = key; 8997 } 8998 result.push(toQueryString(val, deep, transform, fullKey, separator)); 8999 }); 9000 return result.join('&'); 9001 } 9002 9003 function getURIComponentValue(obj, prefix, transform) { 9004 var value; 9005 if (transform) { 9006 value = transform(obj, prefix); 9007 } else if (isDate(obj)) { 9008 value = obj.getTime(); 9009 } else { 9010 value = obj; 9011 } 9012 return sanitizeURIComponent(prefix) + '=' + sanitizeURIComponent(value); 9013 } 9014 9015 function sanitizeURIComponent(obj) { 9016 // undefined, null, and NaN are represented as a blank string, 9017 // while false and 0 are stringified. 9018 return !obj && obj !== false && obj !== 0 ? '' : encodeURIComponent(obj); 9019 } 9020 9021 9022 // Query Strings | Parsing 9023 9024 function fromQueryStringWithOptions(obj, opts) { 9025 var str = String(obj || '').replace(/^.*?\?/, ''), result = {}, auto; 9026 opts = opts || {}; 9027 if (str) { 9028 forEach(str.split('&'), function(p) { 9029 var split = p.split('='); 9030 var key = decodeURIComponent(split[0]); 9031 var val = split.length === 2 ? decodeURIComponent(split[1]) : ''; 9032 auto = opts.auto !== false; 9033 parseQueryComponent(result, key, val, opts.deep, auto, opts.separator, opts.transform); 9034 }); 9035 } 9036 return result; 9037 } 9038 9039 function parseQueryComponent(obj, key, val, deep, auto, separator, transform) { 9040 var match; 9041 if (separator) { 9042 key = mapQuerySeparatorToKeys(key, separator); 9043 deep = true; 9044 } 9045 if (deep === true && (match = key.match(DEEP_QUERY_STRING_REG))) { 9046 parseDeepQueryComponent(obj, match, val, deep, auto, separator, transform); 9047 } else { 9048 setQueryProperty(obj, key, val, auto, transform); 9049 } 9050 } 9051 9052 function parseDeepQueryComponent(obj, match, val, deep, auto, separator, transform) { 9053 var key = match[1]; 9054 var inner = match[2].slice(1, -1).split(']['); 9055 forEach(inner, function(k) { 9056 if (!hasOwn(obj, key)) { 9057 obj[key] = k ? {} : []; 9058 } 9059 obj = getOwn(obj, key); 9060 key = k ? k : obj.length.toString(); 9061 }); 9062 setQueryProperty(obj, key, val, auto, transform); 9063 } 9064 9065 function mapQuerySeparatorToKeys(key, separator) { 9066 var split = key.split(separator), result = split[0]; 9067 for (var i = 1, len = split.length; i < len; i++) { 9068 result += '[' + split[i] + ']'; 9069 } 9070 return result; 9071 } 9072 9073 function setQueryProperty(obj, key, val, auto, transform) { 9074 var fnValue; 9075 if (transform) { 9076 fnValue = transform(val, key, obj); 9077 } 9078 if (isDefined(fnValue)) { 9079 val = fnValue; 9080 } else if (auto) { 9081 val = getQueryValueAuto(obj, key, val); 9082 } 9083 obj[key] = val; 9084 } 9085 9086 function getQueryValueAuto(obj, key, val) { 9087 if (!val) { 9088 return null; 9089 } else if (val === 'true') { 9090 return true; 9091 } else if (val === 'false') { 9092 return false; 9093 } 9094 var num = +val; 9095 if (!isNaN(num) && stringIsDecimal(val)) { 9096 return num; 9097 } 9098 var existing = getOwn(obj, key); 9099 if (val && existing) { 9100 return isArray(existing) ? existing.concat(val) : [existing, val]; 9101 } 9102 return val; 9103 } 9104 9105 function stringIsDecimal(str) { 9106 return str !== '' && !NON_DECIMAL_REG.test(str); 9107 } 9108 9109 9110 // Object Merging 9111 9112 function mergeWithOptions(target, source, opts) { 9113 opts = opts || {}; 9114 return objectMerge(target, source, opts.deep, opts.resolve, opts.hidden, opts.descriptor); 9115 } 9116 9117 function defaults(target, sources, opts) { 9118 opts = opts || {}; 9119 opts.resolve = opts.resolve || false; 9120 return mergeAll(target, sources, opts); 9121 } 9122 9123 function mergeAll(target, sources, opts) { 9124 if (!isArray(sources)) { 9125 sources = [sources]; 9126 }
9127 forEach(sources, function(source) { 9128 return mergeWithOptions(target, source, opts); 9129 }); 9130 return target; 9131 } 9132 9133 function iterateOverProperties(hidden, obj, fn) { 9134 if (getOwnPropertyNames && hidden) { 9135 iterateOverKeys(getOwnPropertyNames, obj, fn, hidden); 9136 } else { 9137 forEachProperty(obj, fn); 9138 } 9139 if (getOwnPropertySymbols) { 9140 iterateOverKeys(getOwnPropertySymbols, obj, fn, hidden); 9141 } 9142 } 9143 9144 // "keys" may include symbols 9145 function iterateOverKeys(getFn, obj, fn, hidden) { 9146 var keys = getFn(obj), desc; 9147 for (var i = 0, key; key = keys[i]; i++) { 9148 desc = getOwnPropertyDescriptor(obj, key); 9149 if (desc.enumerable || hidden) { 9150 fn(obj[key], key); 9151 } 9152 } 9153 } 9154 9155 function mergeByPropertyDescriptor(target, source, prop, sourceVal) { 9156 var descriptor = getOwnPropertyDescriptor(source, prop); 9157 if (isDefined(descriptor.value)) { 9158 descriptor.value = sourceVal; 9159 } 9160 defineProperty(target, prop, descriptor); 9161 } 9162 9163 function objectMerge(target, source, deep, resolve, hidden, descriptor) { 9164 var resolveByFunction = isFunction(resolve), resolveConflicts = resolve !== false; 9165 9166 if (isUndefined(target)) { 9167 target = getNewObjectForMerge(source); 9168 } else if (resolveConflicts && isDate(target) && isDate(source)) { 9169 // A date's timestamp is a property that can only be reached through its 9170 // methods, so actively set it up front if both are dates. 9171 target.setTime(source.getTime()); 9172 } 9173 9174 if (isPrimitive(target)) {
9175 // Will not merge into a primitive type, so simply override. 9176 return source; 9177 } 9178 9179 // If the source object is a primitive 9180 // type then coerce it into an object. 9181 if (isPrimitive(source)) { 9182 source = coercePrimitiveToObject(source); 9183 } 9184 9185 iterateOverProperties(hidden, source, function(val, key) { 9186 var sourceVal, targetVal, resolved, goDeep, result; 9187 9188 sourceVal = source[key]; 9189 9190 // We are iterating over properties of the source, so hasOwnProperty on 9191 // it is guaranteed to always be true. However, the target may happen to 9192 // have properties in its prototype chain that should not be considered 9193 // as conflicts. 9194 targetVal = getOwn(target, key); 9195 9196 if (resolveByFunction) { 9197 result = resolve(key, targetVal, sourceVal, target, source); 9198 if (isUndefined(result)) { 9199 // Result is undefined so do not merge this property. 9200 return; 9201 } else if (isDefined(result) && result !== Sugar) { 9202 // If the source returns anything except undefined, then the conflict 9203 // has been resolved, so don't continue traversing into the object. If 9204 // the returned value is the Sugar global object, then allowing Sugar 9205 // to resolve the conflict, so continue on. 9206 sourceVal = result; 9207 resolved = true; 9208 } 9209 } else if (isUndefined(sourceVal)) {
9210 // Will not merge undefined. 9211 return; 9212 } 9213 9214 // Regex properties are read-only, so intentionally disallowing deep 9215 // merging for now. Instead merge by reference even if deep. 9216 goDeep = !resolved && deep && isObjectType(sourceVal) && !isRegExp(sourceVal); 9217 9218 if (!goDeep && !resolveConflicts && isDefined(targetVal)) { 9219 return; 9220 } 9221 9222 if (goDeep) { 9223 sourceVal = objectMerge(targetVal, sourceVal, deep, resolve, hidden, descriptor); 9224 } 9225 9226 // getOwnPropertyNames is standing in as 9227 // a test for property descriptor support 9228 if (getOwnPropertyNames && descriptor) { 9229 mergeByPropertyDescriptor(target, source, key, sourceVal); 9230 } else { 9231 target[key] = sourceVal; 9232 } 9233 9234 }); 9235 return target; 9236 } 9237 9238 function getNewObjectForMerge(source) { 9239 var klass = classToString(source); 9240 // Primitive types, dates, and regexes have no "empty" state. If they exist 9241 // at all, then they have an associated value. As we are only creating new 9242 // objects when they don't exist in the target, these values can come alone 9243 // for the ride when created. 9244 if (isArray(source, klass)) { 9245 return []; 9246 } else if (isPlainObject(source, klass)) { 9247 return {}; 9248 } else if (isDate(source, klass)) { 9249 return new Date(source.getTime()); 9250 } else if (isRegExp(source, klass)) { 9251 return RegExp(source.source, getRegExpFlags(source)); 9252 } else if (isPrimitive(source && source.valueOf())) { 9253 return source; 9254 } 9255 // If the object is not of a known type, then simply merging its 9256 // properties into a plain object will result in something different 9257 // (it will not respond to instanceof operator etc). Similarly we don't 9258 // want to call a constructor here as we can't know for sure what the 9259 // original constructor was called with (Events etc), so throw an 9260 // error here instead. Non-standard types can be handled if either they 9261 // already exist and simply have their properties merged, if the merge 9262 // is not deep so their references will simply be copied over, or if a 9263 // resolve function is used to assist the merge. 9264 throw new TypeError('Must be a basic data type'); 9265 } 9266 9267 function clone(source, deep) { 9268 var target = getNewObjectForMerge(source); 9269 return objectMerge(target, source, deep, true, true, true); 9270 } 9271 9272 9273 // Keys/Values 9274 9275 function objectSize(obj) { 9276 return getKeysWithObjectCoercion(obj).length; 9277 } 9278 9279 function getKeysWithObjectCoercion(obj) { 9280 return getKeys(coercePrimitiveToObject(obj)); 9281 } 9282 9283 function getValues(obj) { 9284 var values = []; 9285 forEachProperty(obj, function(val) { 9286 values.push(val); 9287 }); 9288 return values; 9289 } 9290 9291 function tap(obj, arg) { 9292 var fn = arg; 9293 if (!isFunction(arg)) { 9294 fn = function() { 9295 if (arg) obj[arg](); 9296 }; 9297 } 9298 fn.call(obj, obj); 9299 return obj; 9300 } 9301 9302 // Select/Reject 9303 9304 function objectSelect(obj, f) { 9305 return selectFromObject(obj, f, true); 9306 } 9307 9308 function objectReject(obj, f) { 9309 return selectFromObject(obj, f, false); 9310 } 9311 9312 function selectFromObject(obj, f, select) { 9313 var match, result = {}; 9314 f = [].concat(f); 9315 forEachProperty(obj, function(val, key) { 9316 match = false; 9317 for (var i = 0; i < f.length; i++) { 9318 if (matchInObject(f[i], key)) { 9319 match = true; 9320 } 9321 } 9322 if (match === select) { 9323 result[key] = val; 9324 } 9325 }); 9326 return result; 9327 } 9328 9329 function matchInObject(match, key) { 9330 if (isRegExp(match)) { 9331 return match.test(key); 9332 } else if (isObjectType(match)) { 9333 return key in match; 9334 } else { 9335 return key === String(match); 9336 } 9337 } 9338 9339 // Remove/Exclude 9340 9341 function objectRemove(obj, f) { 9342 var matcher = getMatcher(f);
9343 forEachProperty(obj, function(val, key) { 9344 if (matcher(val, key, obj)) { 9345 delete obj[key]; 9346 } 9347 }); 9348 return obj; 9349 } 9350 9351 function objectExclude(obj, f) { 9352 var result = {}; 9353 var matcher = getMatcher(f); 9354 forEachProperty(obj, function(val, key) { 9355 if (!matcher(val, key, obj)) { 9356 result[key] = val; 9357 } 9358 }); 9359 return result; 9360 } 9361 9362 function objectIntersectOrSubtract(obj1, obj2, subtract) { 9363 if (!isObjectType(obj1)) { 9364 return subtract ? obj1 : {}; 9365 } 9366 obj2 = coercePrimitiveToObject(obj2); 9367 function resolve(key, val, val1) { 9368 var exists = key in obj2 && isEqual(val1, obj2[key]); 9369 if (exists !== subtract) { 9370 return val1; 9371 } 9372 } 9373 return objectMerge({}, obj1, false, resolve); 9374 } 9375 9376 /*** 9377 * @method is[Type]() 9378 * @returns Boolean 9379 * @short Returns true if the object is an object of that type. 9380 * 9381 * @set 9382 * isArray 9383 * isBoolean 9384 * isDate 9385 * isError 9386 * isFunction 9387 * isMap 9388 * isNumber 9389 * isRegExp 9390 * isSet 9391 * isString 9392 * 9393 * @example 9394 * 9395 * Object.isArray([3]) -> true 9396 * Object.isNumber(3) -> true 9397 * Object.isString(8) -> false 9398 * 9399 ***/ 9400 function buildClassCheckMethods() { 9401 var checks = [isBoolean, isNumber, isString, isDate, isRegExp, isFunction, isArray, isError, isSet, isMap]; 9402 defineInstanceAndStaticSimilar(sugarObject, NATIVE_TYPES, function(methods, name, i) { 9403 methods['is' + name] = checks[i]; 9404 }); 9405 } 9406 9407 defineStatic(sugarObject, { 9408 9409 /*** 9410 * @method fromQueryString(str, [options]) 9411 * @returns Object 9412 * @static 9413 * @short Converts the query string of a URL into an object. 9414 * @extra Options can be passed with [options] for more control over the result. 9415 * 9416 * @options 9417 * 9418 * deep If the string contains "deep" syntax `foo[]`, this will 9419 * be automatically converted to an array. (Default `false`) 9420 * 9421 * auto If `true`, booleans `"true"` and `"false"`, numbers, and arrays 9422 * (repeated keys) will be automatically cast to native 9423 * values. (Default `true`) 9424 * 9425 * transform A function whose return value becomes the final value. If 9426 * the function returns `undefined`, then the original value 9427 * will be used. This allows the function to intercept only 9428 * certain keys or values. (Default `undefined`) 9429 * 9430 * separator If passed, keys will be split on this string to extract 9431 * deep values. (Default `''`) 9432 * 9433 * @callback queryStringTransformFn 9434 * 9435 * key The key component of the query string (before `=`). 9436 * val The value component of the query string (after `=`). 9437 * obj A reference to the object being built. 9438 * 9439 * @example 9440 * 9441 * Object.fromQueryString('a=1&b=2') -> {a:1,b:2} 9442 * Object.fromQueryString('a[]=1&a[]=2',{deep:true}) -> {a:['1','2']} 9443 * Object.fromQueryString('a_b=c',{separator:'_'}) -> {a:{b:'c'}} 9444 * Object.fromQueryString('id=123', {transform:idToNumber}); 9445 * 9446 * @param {string} str 9447 * @param {QueryStringParseOptions} options 9448 * @callbackParam {string} key 9449 * @callbackParam {Property} val 9450 * @callbackParam {Object} obj 9451 * @callbackReturns {NewProperty} queryStringTransformFn 9452 * @option {boolean} [deep] 9453 * @option {boolean} [auto] 9454 * @option {string} [separator] 9455 * @option {queryStringTransformFn} [transform] 9456 * 9457 ***/ 9458 'fromQueryString': function(obj, options) { 9459 return fromQueryStringWithOptions(obj, options); 9460 } 9461 9462 });
9463 9464 defineInstanceAndStatic(sugarObject, { 9465 9466 /*** 9467 * @method has(key, [inherited] = false) 9468 * @returns Boolean 9469 * @short Checks if the object has property `key`. 9470 * @extra Supports `deep properties`. If [inherited] is `true`, 9471 * properties defined in the prototype chain will also return `true`. 9472 * The default of `false` for this argument makes this method suited 9473 * to working with objects as data stores by default. 9474 * 9475 * @example 9476 * 9477 * Object.has(usersByName, 'Harry') -> true 9478 * Object.has(data, 'users[1].profile') -> true 9479 * Object.has([], 'forEach') -> false 9480 * Object.has([], 'forEach', true) -> true 9481 * 9482 * @param {string} key 9483 * @param {boolean} [inherited] 9484 * 9485 ***/ 9486 'has': function(obj, key, any) { 9487 return deepHasProperty(obj, key, any); 9488 }, 9489 9490 /*** 9491 * @method get(key, [inherited] = false) 9492 * @returns Mixed 9493 * @short Gets a property of the object. 9494 * @extra Supports `deep properties`. If [inherited] is `true`, 9495 * properties defined in the prototype chain will also be returned. 9496 * The default of `false` for this argument makes this method suited 9497 * to working with objects as data stores by default. 9498 * 9499 * @example 9500 * 9501 * Object.get(Harry, 'name'); -> 'Harry' 9502 * Object.get(Harry, 'profile.likes'); -> Harry's likes 9503 * Object.get(data, 'users[3].name') -> User 3's name 9504 * Object.get(data, 'users[1..2]') -> Users 1 and 2 9505 * Object.get(data, 'users[1..2].name') -> Names of users 1 and 2 9506 * Object.get(data, 'users[-2..-1]') -> Last 2 users 9507 * 9508 * @param {string} key 9509 * @param {boolean} [inherited] 9510 * 9511 ***/ 9512 'get': function(obj, key, any) { 9513 return deepGetProperty(obj, key, any); 9514 }, 9515 9516 /*** 9517 * @method set(key, val) 9518 * @returns Object 9519 * @short Sets a property on the object. 9520 * @extra Using a dot or square bracket in `key` is considered "deep" syntax, 9521 * and will attempt to traverse into the object to set the property, 9522 * creating properties that do not exist along the way. If the missing 9523 * property is referenced using square brackets, an empty array will be 9524 * created, otherwise an empty object. A special `[]` carries the 9525 * meaning of "the last index + 1", and will effectively push `val` 9526 * onto the end of the array. Lastly, a `..` separator inside the 9527 * brackets is "range" notation, and will set properties on all 9528 * elements in the specified range. Range members may be negative, 9529 * which will be offset from the end of the array. 9530 * 9531 * @example 9532 * 9533 * Object.set({}, 'name', 'Harry'); -> {name:'Harry'} 9534 * Object.set({}, 'user.name', 'Harry'); -> {user:{name:'Harry'}} 9535 * Object.set({}, 'users[].name', 'Bob') -> {users:[{name:'Bob'}} 9536 * Object.set({}, 'users[1].name','Bob') -> {users:[undefined, {name:'Bob'}]} 9537 * Object.set({}, 'users[0..1].name','Bob') -> {users:[{name:'Bob'},{name:'Bob'}]} 9538 * 9539 * @param {string} key 9540 * @param {Property} val 9541 * 9542 ***/ 9543 'set': function(obj, key, val) { 9544 return deepSetProperty(obj, key, val); 9545 }, 9546 9547 /*** 9548 * @method size() 9549 * @returns Number 9550 * @short Returns the number of properties in the object. 9551 * 9552 * @example 9553 * 9554 * Object.size({foo:'bar'}) -> 1 9555 * 9556 ***/ 9557 'size': function(obj) { 9558 return objectSize(obj); 9559 }, 9560 9561 /*** 9562 * @method isEmpty() 9563 * @returns Boolean 9564 * @short Returns true if the number of properties in the object is zero. 9565 * 9566 * @example 9567 * 9568 * Object.isEmpty({}) -> true 9569 * Object.isEmpty({a:1}) -> false 9570 * 9571 ***/ 9572 'isEmpty': function(obj) { 9573 return objectSize(obj) === 0; 9574 }, 9575 9576 /*** 9577 * @method toQueryString([options]) 9578 * @returns Object 9579 * @short Converts the object into a query string. 9580 * @extra Accepts deep objects and arrays. [options] can be passed for more 9581 * control over the result. 9582 * 9583 * @options 9584 * 9585 * deep If `true`, non-standard "deep" syntax `foo[]` will be 9586 * used for output. Note that `separator` will be ignored, 9587 * as this option overrides shallow syntax. (Default `false`) 9588 * 9589 * prefix If passed, this string will be prefixed to all keys, 9590 * separated by the `separator`. (Default `''`). 9591 * 9592 * transform A function whose return value becomes the final value 9593 * in the string. (Default `undefined`) 9594 * 9595 * separator A string that is used to separate keys, either for deep 9596 * objects, or when `prefix` is passed.(Default `_`). 9597 * 9598 * @callback queryStringTransformFn 9599 * 9600 * key The key of the current iteration. 9601 * val The value of the current iteration. 9602 * obj A reference to the object. 9603 * 9604 * @example 9605 * 9606 * Object.toQueryString({foo:'bar'}) -> 'foo=bar' 9607 * Object.toQueryString({foo:['a','b']}) -> 'foo=a&foo=b' 9608 * Object.toQueryString({foo:['a','b']}, {deep:true}) -> 'foo[]=a&foo[]=b' 9609 * 9610 * @param {Object} obj 9611 * @param {QueryStringOptions} [options] 9612 * @callbackParam {string} key 9613 * @callbackParam {Property} val 9614 * @callbackParam {Object} obj 9615 * @callbackReturns {NewProperty} queryStringTransformFn 9616 * @option {boolean} [deep] 9617 * @option {string} [prefix] 9618 * @option {string} [separator] 9619 * @option {queryStringTransformFn} [transform] 9620 * 9621 ***/ 9622 'toQueryString': function(obj, options) { 9623 return toQueryStringWithOptions(obj, options); 9624 }, 9625 9626 /*** 9627 * @method isEqual(obj) 9628 * @returns Boolean 9629 * @short Returns true if `obj` is equivalent to the object. 9630 * @extra If both objects are built-in types, they will be considered 9631 * equivalent if they are not "observably distinguishable". This means 9632 * that primitives and object types, `0` and `-0`, and sparse and 9633 * dense arrays are all not equal. Functions and non-built-ins like 9634 * instances of user-defined classes and host objects like Element and 9635 * Event are strictly compared `===`, and will only be equal if they 9636 * are the same reference. Plain objects as well as Arrays will be 9637 * traversed into and deeply checked by their non-inherited, enumerable 9638 * properties. Other allowed types include Typed Arrays, Sets, Maps, 9639 * Arguments, Dates, Regexes, and Errors. 9640 * 9641 * @example 9642 * 9643 * Object.isEqual({a:2}, {a:2}) -> true 9644 * Object.isEqual({a:2}, {a:3}) -> false 9645 * Object.isEqual(5, Object(5)) -> false 9646 * Object.isEqual(Object(5), Object(5)) -> true 9647 * Object.isEqual(NaN, NaN) -> false 9648 * 9649 * @param {Object} obj 9650 * 9651 ***/ 9652 'isEqual': function(obj1, obj2) { 9653 return isEqual(obj1, obj2); 9654 }, 9655 9656 /*** 9657 * @method merge(source, [options]) 9658 * @returns Object 9659 * @short Merges properties from `source` into the object. 9660 * @extra This method will modify the object! Use `add` for a non-destructive 9661 * alias. 9662 * 9663 * @options 9664 * 9665 * deep If `true` deep properties are merged recursively. 9666 * (Default `false`) 9667 * 9668 * hidden If `true`, non-enumerable properties will be merged as well. 9669 * (Default `false`) 9670 * 9671 * descriptor If `true`, properties will be merged by property descriptor. 9672 * Use this option to merge getters or setters, or to preserve 9673 * `enumerable`, `configurable`, etc. 9674 * (Default `false`) 9675 * 9676 * resolve Determines which property wins in the case of conflicts. 9677 * If `true`, `source` wins. If `false`, the original property wins. 9678 * If a function is passed, its return value will decide the result. 9679 * Any non-undefined return value will resolve the conflict 9680 * for that property (will not continue if `deep`). Returning 9681 * `undefined` will do nothing (no merge). Finally, returning 9682 * the global object `Sugar` will allow Sugar to handle the 9683 * merge as normal. (Default `true`) 9684 * 9685 * @callback resolveFn 9686 * 9687 * key The key of the current iteration. 9688 * targetVal The current value for the key in the target. 9689 * sourceVal The current value for the key in `source`. 9690 * target The target object. 9691 * source The source object. 9692 * 9693 * @example 9694 * 9695 * Object.merge({one:1},{two:2}) -> {one:1,two:2} 9696 * Object.merge({one:1},{one:9,two:2}) -> {one:9,two:2} 9697 * Object.merge({x:{a:1}},{x:{b:2}},{deep:true}) -> {x:{a:1,b:2}} 9698 * Object.merge({a:1},{a:2},{resolve:mergeAdd}) -> {a:3} 9699 * 9700 * @param {Object} source 9701 * @param {ObjectMergeOptions} [options] 9702 * @callbackParam {string} key 9703 * @callbackParam {Property} targetVal 9704 * @callbackParam {Property} sourceVal 9705 * @callbackParam {Object} target 9706 * @callbackParam {Object} source 9707 * @callbackReturns {boolean} resolveFn 9708 * @option {boolean} [deep] 9709 * @option {boolean} [hidden] 9710 * @option {boolean} [descriptor] 9711 * @option {boolean|resolveFn} [resolve] 9712 * 9713 ***/ 9714 'merge': function(target, source, opts) { 9715 return mergeWithOptions(target, source, opts); 9716 }, 9717 9718 /*** 9719 * @method add(obj, [options]) 9720 * @returns Object 9721 * @short Adds properties in `obj` and returns a new object. 9722 * @extra This method will not modify the original object. See `merge` for options. 9723 * 9724 * @example 9725 * 9726 * Object.add({one:1},{two:2}) -> {one:1,two:2} 9727 * Object.add({one:1},{one:9,two:2}) -> {one:9,two:2} 9728 * Object.add({x:{a:1}},{x:{b:2}},{deep:true}) -> {x:{a:1,b:2}} 9729 * Object.add({a:1},{a:2},{resolve:mergeAdd}) -> {a:3} 9730 * 9731 * @param {Object} obj 9732 * @param {ObjectMergeOptions} [options] 9733 * 9734 ***/ 9735 'add': function(obj1, obj2, opts) { 9736 return mergeWithOptions(clone(obj1), obj2, opts); 9737 }, 9738 9739 /*** 9740 * @method mergeAll(sources, [options]) 9741 * @returns Object 9742 * @short Merges properties from an array of `sources`. 9743 * @extra This method will modify the object! Use `addAll` for a non-destructive 9744 * alias. See `merge` for options. 9745 * 9746 * @example 9747 * 9748 * Object.mergeAll({one:1},[{two:2},{three:3}]) -> {one:1,two:2,three:3} 9749 * Object.mergeAll({x:{a:1}},[{x:{b:2}},{x:{c:3}}],{deep:true}) -> {x:{a:1,b:2,c:3}} 9750 * 9751 * @param {Array<Object>} sources 9752 * @param {ObjectMergeOptions} [options] 9753 * 9754 ***/ 9755 'mergeAll': function(target, sources, opts) { 9756 return mergeAll(target, sources, opts); 9757 }, 9758 9759 /*** 9760 * @method addAll(sources, [options]) 9761 * @returns Object 9762 * @short Adds properties from an array of `sources` and returns a new object. 9763 * @extra This method will not modify the object. See `merge` for options. 9764 * 9765 * @example 9766 *
9767 * Object.addAll({one:1},[{two:2},{three:3}]) -> {one:1,two:2,three:3} 9768 * Object.addAll({x:{a:1}},[{x:{b:2}},{x:{c:3}}],{deep:true}) -> {x:{a:1,b:2,c:3}} 9769 * 9770 * @param {Array<Object>} sources 9771 * @param {ObjectMergeOptions} [options] 9772 * 9773 ***/ 9774 'addAll': function(obj, sources, opts) { 9775 return mergeAll(clone(obj), sources, opts); 9776 }, 9777 9778 /*** 9779 * @method defaults(sources, [options]) 9780 * @returns Object 9781 * @short Merges properties from one or multiple `sources` while preserving 9782 * the object's defined properties. 9783 * @extra This method modifies the object! See `merge` for options. 9784 * 9785 * @example 9786 * 9787 * Object.defaults({one:1},[{one:9},{two:2}]) -> {one:1,two:2} 9788 * Object.defaults({x:{a:1}},[{x:{a:9}},{x:{b:2}}],{deep:true}) -> {x:{a:1,b:2}} 9789 * 9790 * @param {Array<Object>} sources 9791 * @param {ObjectMergeOptions} [options] 9792 * 9793 ***/ 9794 'defaults': function(target, sources, opts) { 9795 return defaults(target, sources, opts); 9796 }, 9797 9798 /*** 9799 * @method intersect(obj) 9800 * @returns Object 9801 * @short Returns a new object whose properties are those that the object has 9802 * in common both with `obj`. 9803 * @extra If both key and value do not match, then the property will not be included. 9804 * 9805 * @example 9806 * 9807 * Object.intersect({a:'a'},{b:'b'}) -> {} 9808 * Object.intersect({a:'a'},{a:'b'}) -> {} 9809 * Object.intersect({a:'a',b:'b'},{b:'b',z:'z'}) -> {b:'b'} 9810 * 9811 * @param {Object} obj 9812 * 9813 ***/ 9814 'intersect': function(obj1, obj2) { 9815 return objectIntersectOrSubtract(obj1, obj2, false); 9816 }, 9817 9818 /*** 9819 * @method subtract(obj) 9820 * @returns Object 9821 * @short Returns a clone of the object with any properties shared with `obj` excluded. 9822 * @extra If both key and value do not match, then the property will not be excluded. 9823 * 9824 * @example 9825 * 9826 * Object.subtract({a:'a',b:'b'},{b:'b'}) -> {a:'a'} 9827 * Object.subtract({a:'a',b:'b'},{a:'b'}) -> {a:'a',b:'b'} 9828 * 9829 * @param {Object} obj 9830 * 9831 ***/ 9832 'subtract': function(obj1, obj2) { 9833 return objectIntersectOrSubtract(obj1, obj2, true); 9834 }, 9835 9836 /*** 9837 * @method clone([deep] = false) 9838 * @returns Object 9839 * @short Creates a clone of the object. 9840 * @extra Default is a shallow clone, unless [deep] is true. 9841 * 9842 * @example 9843 * 9844 * Object.clone({foo:'bar'}) -> creates shallow clone 9845 * Object.clone({foo:'bar'}, true) -> creates a deep clone 9846 * 9847 * @param {boolean} [deep] 9848 * 9849 ***/ 9850 'clone': function(obj, deep) { 9851 return clone(obj, deep); 9852 }, 9853 9854 /*** 9855 * @method values() 9856 * @returns Array 9857 * @short Returns an array containing the values in the object. 9858 * @extra Values are in no particular order. Does not include inherited or 9859 * non-enumerable properties. 9860 * 9861 * @example 9862 * 9863 * Object.values({a:'a',b:'b'}) -> ['a','b'] 9864 * 9865 ***/ 9866 'values': function(obj) { 9867 return getValues(obj); 9868 }, 9869 9870 /*** 9871 * @method invert([multi] = false) 9872 * @returns Object 9873 * @short Creates a new object with the keys and values swapped. 9874 * @extra If [multi] is true, values will be an array of all keys, othewise 9875 * collisions will be overwritten. 9876 * 9877 * @example 9878 * 9879 * Object.invert({foo:'bar'}) -> {bar:'foo'} 9880 * Object.invert({a:1,b:1}, true) -> {1:['a','b']} 9881 * 9882 * @param {boolean} [multi] 9883 * 9884 ***/ 9885 'invert': function(obj, multi) { 9886 var result = {}; 9887 multi = multi === true;
9888 forEachProperty(obj, function(val, key) { 9889 if (hasOwn(result, val) && multi) { 9890 result[val].push(key); 9891 } else if (multi) { 9892 result[val] = [key]; 9893 } else { 9894 result[val] = key; 9895 } 9896 }); 9897 return result; 9898 }, 9899 9900 /*** 9901 * @method tap(fn) 9902 * @returns Object 9903 * @short Runs `fn` and returns the object. 9904 * @extra A string can also be used as a shortcut to a method. This method is 9905 * designed to run an intermediary function that "taps into" a method 9906 * chain. As such, it is fairly useless as a static method. However it 9907 * can be quite useful when combined with chainables. 9908 * 9909 * @callback tapFn 9910 * 9911 * obj A reference to the object. 9912 * 9913 * @example 9914 * 9915 * Sugar.Array([1,4,9]).map(Math.sqrt).tap('pop') -> [1,2] 9916 * Sugar.Object({a:'a'}).tap(logArgs).merge({b:'b'}) -> {a:'a',b:'b'} 9917 * 9918 * @param {tapFn} fn 9919 * @callbackParam {Object} obj 9920 * @callbackReturns {any} tapFn 9921 * 9922 ***/ 9923 'tap': function(obj, arg) { 9924 return tap(obj, arg); 9925 }, 9926 9927 /*** 9928 * @method isArguments() 9929 * @returns Boolean 9930 * @short Returns true if the object is an arguments object. 9931 * 9932 * @example 9933 * 9934 * Object.isArguments([1]) -> false 9935 * 9936 ***/ 9937 'isArguments': function(obj) { 9938 return isArguments(obj); 9939 }, 9940 9941 /*** 9942 * @method isObject() 9943 * @returns Boolean 9944 * @short Returns true if the object is a "plain" object. 9945 * @extra Plain objects do not include instances of classes or "host" objects, 9946 * such as Elements, Events, etc. 9947 * 9948 * @example 9949 * 9950 * Object.isObject({ broken:'wear' }) -> true 9951 * 9952 ***/ 9953 'isObject': function(obj) { 9954 return isPlainObject(obj); 9955 }, 9956 9957 /*** 9958 * @method remove(search) 9959 * @returns Object 9960 * @short Deletes all properties in the object matching `search`. 9961 * @extra This method will modify the object!. Implements `enhanced matching`. 9962 * 9963 * @callback searchFn 9964 * 9965 * key The key of the current iteration. 9966 * val The value of the current iteration. 9967 * obj A reference to the object. 9968 * 9969 * @example 9970 * 9971 * Object.remove({a:'a',b:'b'}, 'a'); -> {b:'b'} 9972 * Object.remove({a:'a',b:'b',z:'z'}, /[a-f]/); -> {z:'z'} 9973 * 9974 * @param {Property|searchFn} search 9975 * @callbackParam {Property} val 9976 * @callbackParam {string} key 9977 * @callbackParam {Object} obj 9978 * @callbackReturns {boolean} searchFn 9979 * 9980 ***/ 9981 'remove': function(obj, f) { 9982 return objectRemove(obj, f); 9983 }, 9984 9985 /*** 9986 * @method exclude(search) 9987 * @returns Object 9988 * @short Returns a new object with all properties matching `search` removed. 9989 * @extra This is a non-destructive version of `remove` and will not modify 9990 * the object. Implements `enhanced matching`. 9991 * 9992 * @callback searchFn 9993 * 9994 * key The key of the current iteration. 9995 * val The value of the current iteration. 9996 * obj A reference to the object. 9997 * 9998 * @example 9999 * 10000 * Object.exclude({a:'a',b:'b'}, 'a'); -> {b:'b'} 10001 * Object.exclude({a:'a',b:'b',z:'z'}, /[a-f]/); -> {z:'z'} 10002 * 10003 * @param {Property|searchFn} search 10004 * @callbackParam {Property} val 10005 * @callbackParam {string} key 10006 * @callbackParam {Object} obj 10007 * @callbackReturns {boolean} searchFn 10008 * 10009 ***/ 10010 'exclude': function(obj, f) { 10011 return objectExclude(obj, f); 10012 }, 10013 10014 /*** 10015 * @method select(find) 10016 * @returns Object
10017 * @short Builds a new object containing the keys specified in `find`. 10018 * @extra When `find` is a string, a single key will be selected. Arrays or 10019 * objects match multiple keys, and a regex will match keys by regex. 10020 * 10021 * @example 10022 * 10023 * Object.select({a:1,b:2}, 'a') -> {a:1} 10024 * Object.select({a:1,b:2}, ['a', 'b']) -> {a:1,b:2} 10025 * Object.select({a:1,b:2}, /[a-z]/) -> {a:1,b:2} 10026 * Object.select({a:1,b:2}, {a:'a',b:'b'}) -> {a:1,b:2} 10027 * 10028 * @param {string|RegExp|Array<string>|Object} find 10029 * 10030 ***/ 10031 'select': function(obj, f) { 10032 return objectSelect(obj, f); 10033 }, 10034 10035 /*** 10036 * @method reject(find) 10037 * @returns Object 10038 * @short Builds a new object containing all keys except those in `find`. 10039 * @extra When `find` is a string, a single key will be rejected. Arrays or 10040 * objects match multiple keys, and a regex will match keys by regex. 10041 * 10042 * @example 10043 * 10044 * Object.reject({a:1,b:2}, 'a') -> {b:2} 10045 * Object.reject({a:1,b:2}, /[a-z]/) -> {} 10046 * Object.reject({a:1,b:2}, {a:'a'}) -> {b:2} 10047 * Object.reject({a:1,b:2}, ['a', 'b']) -> {} 10048 * 10049 * @param {string|RegExp|Array<string>|Object} find 10050 * 10051 ***/ 10052 'reject': function(obj, f) { 10053 return objectReject(obj, f); 10054 } 10055 10056 }); 10057 10058 // TODO: why is this here? 10059 defineInstance(sugarObject, { 10060 10061 /*** 10062 * @method keys() 10063 * @returns Array 10064 * @polyfill ES5 10065 * @short Returns an array containing the keys of all of the non-inherited, 10066 * enumerable properties of the object. 10067 * 10068 * @example 10069 * 10070 * Object.keys({a:'a',b:'b'}) -> ['a','b'] 10071 * 10072 ***/ 10073 'keys': function(obj) { 10074 return getKeys(obj); 10075 } 10076 10077 }); 10078 10079 buildClassCheckMethods(); 10080 10081 /*** 10082 * @module Enumerable 10083 * @description Counting, mapping, and finding methods on both arrays and objects. 10084 * 10085 ***/ 10086 10087 function sum(obj, map) { 10088 var sum = 0; 10089 enumerateWithMapping(obj, map, function(val) { 10090 sum += val; 10091 }); 10092 return sum; 10093 } 10094 10095 function average(obj, map) { 10096 var sum = 0, count = 0; 10097 enumerateWithMapping(obj, map, function(val) { 10098 sum += val; 10099 count++; 10100 }); 10101 // Prevent divide by 0 10102 return sum / (count || 1); 10103 } 10104 10105 function median(obj, map) { 10106 var result = [], middle, len; 10107 enumerateWithMapping(obj, map, function(val) { 10108 result.push(val); 10109 }); 10110 len = result.length; 10111 if (!len) return 0; 10112 result.sort(function(a, b) { 10113 // IE7 will throw errors on non-numbers! 10114 return (a || 0) - (b || 0); 10115 }); 10116 middle = trunc(len / 2); 10117 return len % 2 ? result[middle] : (result[middle - 1] + result[middle]) / 2; 10118 } 10119 10120 function getMinOrMax(obj, arg1, arg2, max, asObject) { 10121 var result = [], pushVal, edge, all, map; 10122 if (isBoolean(arg1)) { 10123 all = arg1; 10124 map = arg2; 10125 } else { 10126 map = arg1; 10127 } 10128 enumerateWithMapping(obj, map, function(val, key) { 10129 if (isUndefined(val)) { 10130 throw new TypeError('Cannot compare with undefined'); 10131 } 10132 pushVal = asObject ? key : obj[key]; 10133 if (val === edge) { 10134 result.push(pushVal); 10135 } else if (isUndefined(edge) || (max && val > edge) || (!max && val < edge)) { 10136 result = [pushVal]; 10137 edge = val; 10138 } 10139 }); 10140 return getReducedMinMaxResult(result, obj, all, asObject); 10141 } 10142 10143 function getLeastOrMost(obj, arg1, arg2, most, asObject) { 10144 var group = {}, refs = [], minMaxResult, result, all, map; 10145 if (isBoolean(arg1)) { 10146 all = arg1; 10147 map = arg2; 10148 } else { 10149 map = arg1; 10150 } 10151 enumerateWithMapping(obj, map, function(val, key) { 10152 var groupKey = serializeInternal(val, refs); 10153 var arr = getOwn(group, groupKey) || []; 10154 arr.push(asObject ? key : obj[key]); 10155 group[groupKey] = arr; 10156 }); 10157 minMaxResult = getMinOrMax(group, !!all, 'length', most, true); 10158 if (all) { 10159 result = []; 10160 // Flatten result
10161 forEachProperty(minMaxResult, function(val) { 10162 result = result.concat(val); 10163 }); 10164 } else { 10165 result = getOwn(group, minMaxResult); 10166 } 10167 return getReducedMinMaxResult(result, obj, all, asObject); 10168 } 10169 10170 10171 // Support 10172 10173 function getReducedMinMaxResult(result, obj, all, asObject) { 10174 if (asObject && all) { 10175 // The method has returned an array of keys so use this array 10176 // to build up the resulting object in the form we want it in. 10177 return result.reduce(function(o, key) { 10178 o[key] = obj[key]; 10179 return o; 10180 }, {}); 10181 } else if (result && !all) { 10182 result = result[0]; 10183 } 10184 return result; 10185 } 10186 10187 function enumerateWithMapping(obj, map, fn) { 10188 var arrayIndexes = isArray(obj); 10189 forEachProperty(obj, function(val, key) { 10190 if (arrayIndexes) { 10191 if (!isArrayIndex(key)) { 10192 return; 10193 } 10194 key = +key; 10195 } 10196 var mapped = mapWithShortcuts(val, map, obj, [val, key, obj]); 10197 fn(mapped, key); 10198 }); 10199 } 10200 10201 /*** @namespace Array ***/ 10202 10203 // Flag allowing native array methods to be enhanced 10204 var ARRAY_ENHANCEMENTS_FLAG = 'enhanceArray'; 10205 10206 // Enhanced map function 10207 var enhancedMap = buildEnhancedMapping('map'); 10208 10209 // Enhanced matcher methods 10210 var enhancedFind = buildEnhancedMatching('find'), 10211 enhancedSome = buildEnhancedMatching('some'), 10212 enhancedEvery = buildEnhancedMatching('every'), 10213 enhancedFilter = buildEnhancedMatching('filter'), 10214 enhancedFindIndex = buildEnhancedMatching('findIndex'); 10215 10216 function arrayNone() { 10217 return !enhancedSome.apply(this, arguments); 10218 } 10219 10220 function arrayCount(arr, f) { 10221 if (isUndefined(f)) { 10222 return arr.length; 10223 } 10224 return enhancedFilter.apply(this, arguments).length; 10225 } 10226 10227 // Enhanced methods 10228 10229 function buildEnhancedMapping(name) { 10230 return wrapNativeArrayMethod(name, enhancedMapping); 10231 } 10232 10233 10234 function buildEnhancedMatching(name) { 10235 return wrapNativeArrayMethod(name, enhancedMatching); 10236 } 10237 10238 function enhancedMapping(map, context) { 10239 if (isFunction(map)) { 10240 return map; 10241 } else if (map) { 10242 return function(el, i, arr) { 10243 return mapWithShortcuts(el, map, context, [el, i, arr]); 10244 }; 10245 } 10246 } 10247 10248 function enhancedMatching(f) { 10249 var matcher; 10250 if (isFunction(f)) { 10251 return f; 10252 } 10253 matcher = getMatcher(f); 10254 return function(el, i, arr) { 10255 return matcher(el, i, arr); 10256 }; 10257 } 10258 10259 function wrapNativeArrayMethod(methodName, wrapper) { 10260 var nativeFn = Array.prototype[methodName]; 10261 return function(arr, f, context, argsLen) { 10262 var args = new Array(2); 10263 assertArgument(argsLen > 0); 10264 args[0] = wrapper(f, context); 10265 args[1] = context; 10266 return nativeFn.apply(arr, args); 10267 }; 10268 } 10269 10270 10271 /*** 10272 * @method [fn]FromIndex(startIndex, [loop], ...) 10273 * @returns Mixed 10274 * @short Runs native array functions beginning from `startIndex`. 10275 * @extra If [loop] is `true`, once the end of the array has been reached, 10276 * iteration will continue from the start of the array up to 10277 * `startIndex - 1`. If [loop] is false it can be omitted. Standard 10278 * arguments are then passed which will be forwarded to the native 10279 * methods. When available, methods are always `enhanced`. This includes 10280 * `deep properties` for `map`, and `enhanced matching` for `some`, 10281 * `every`, `filter`, `find`, and `findIndex`. Note also that 10282 * `forEachFromIndex` is optimized for sparse arrays and may be faster 10283 * than native `forEach`. 10284 * 10285 * @set 10286 * mapFromIndex
10287 * forEachFromIndex 10288 * filterFromIndex 10289 * someFromIndex 10290 * everyFromIndex 10291 * reduceFromIndex 10292 * reduceRightFromIndex 10293 * findFromIndex 10294 * findIndexFromIndex 10295 * 10296 * @example 10297 * 10298 * users.mapFromIndex(2, 'name'); 10299 * users.mapFromIndex(2, true, 'name'); 10300 * names.forEachFromIndex(10, log); 10301 * names.everyFromIndex(15, /^[A-F]/); 10302 * 10303 * @signature [fn]FromIndex(startIndex, ...) 10304 * @param {number} startIndex 10305 * @param {boolean} loop 10306 * 10307 ***/ 10308 function buildFromIndexMethods() { 10309 10310 var methods = { 10311 'forEach': { 10312 base: forEachAsNative 10313 }, 10314 'map': { 10315 wrapper: enhancedMapping 10316 }, 10317 'some every': { 10318 wrapper: enhancedMatching 10319 }, 10320 'findIndex': { 10321 wrapper: enhancedMatching, 10322 result: indexResult 10323 }, 10324 'reduce': { 10325 apply: applyReduce 10326 }, 10327 'filter find': { 10328 wrapper: enhancedMatching 10329 }, 10330 'reduceRight': { 10331 apply: applyReduce, 10332 slice: sliceArrayFromRight, 10333 clamp: clampStartIndexFromRight 10334 } 10335 }; 10336 10337 forEachProperty(methods, function(opts, key) { 10338 forEach(spaceSplit(key), function(baseName) { 10339 var methodName = baseName + 'FromIndex'; 10340 var fn = createFromIndexWithOptions(baseName, opts); 10341 defineInstanceWithArguments(sugarArray, methodName, fn); 10342 }); 10343 }); 10344 10345 function forEachAsNative(fn) { 10346 forEach(this, fn); 10347 } 10348 10349 // Methods like filter and find have a direct association between the value 10350 // returned by the callback and the element of the current iteration. This 10351 // means that when looping, array elements must match the actual index for 10352 // which they are being called, so the array must be sliced. This is not the 10353 // case for methods like forEach and map, which either do not use return 10354 // values or use them in a way that simply getting the element at a shifted 10355 // index will not affect the final return value. However, these methods will 10356 // still fail on sparse arrays, so always slicing them here. For example, if 10357 // "forEachFromIndex" were to be called on [1,,2] from index 1, although the 10358 // actual index 1 would itself would be skipped, when the array loops back to 10359 // index 0, shifting it by adding 1 would result in the element for that 10360 // iteration being undefined. For shifting to work, all gaps in the array 10361 // between the actual index and the shifted index would have to be accounted 10362 // for. This is infeasible and is easily solved by simply slicing the actual 10363 // array instead so that gaps align. Note also that in the case of forEach, 10364 // we are using the internal function which handles sparse arrays in a way 10365 // that does not increment the index, and so is highly optimized compared to 10366 // the others here, which are simply going through the native implementation. 10367 function sliceArrayFromLeft(arr, startIndex, loop) { 10368 var result = arr; 10369 if (startIndex) { 10370 result = arr.slice(startIndex); 10371 if (loop) { 10372 result = result.concat(arr.slice(0, startIndex)); 10373 } 10374 } 10375 return result; 10376 } 10377 10378 // When iterating from the right, indexes are effectively shifted by 1. 10379 // For example, iterating from the right from index 2 in an array of 3 10380 // should also include the last element in the array. This matches the 10381 // "lastIndexOf" method which also iterates from the right. 10382 function sliceArrayFromRight(arr, startIndex, loop) { 10383 if (!loop) { 10384 startIndex += 1;
10385 arr = arr.slice(0, max(0, startIndex)); 10386 } 10387 return arr; 10388 } 10389 10390 function clampStartIndex(startIndex, len) { 10391 return min(len, max(0, startIndex)); 10392 } 10393 10394 // As indexes are shifted by 1 when starting from the right, clamping has to 10395 // go down to -1 to accommodate the full range of the sliced array. 10396 function clampStartIndexFromRight(startIndex, len) { 10397 return min(len, max(-1, startIndex)); 10398 } 10399 10400 function applyReduce(arr, startIndex, fn, context, len, loop) { 10401 return function(acc, val, i) { 10402 i = getNormalizedIndex(i + startIndex, len, loop); 10403 return fn.call(arr, acc, val, i, arr); 10404 }; 10405 } 10406 10407 function applyEach(arr, startIndex, fn, context, len, loop) { 10408 return function(el, i) { 10409 i = getNormalizedIndex(i + startIndex, len, loop); 10410 return fn.call(context, arr[i], i, arr); 10411 }; 10412 } 10413 10414 function indexResult(result, startIndex, len) { 10415 if (result !== -1) { 10416 result = (result + startIndex) % len; 10417 } 10418 return result; 10419 } 10420 10421 function createFromIndexWithOptions(methodName, opts) { 10422 10423 var baseFn = opts.base || Array.prototype[methodName], 10424 applyCallback = opts.apply || applyEach, 10425 sliceArray = opts.slice || sliceArrayFromLeft, 10426 clampIndex = opts.clamp || clampStartIndex, 10427 getResult = opts.result, 10428 wrapper = opts.wrapper; 10429 10430 return function(arr, startIndex, args) { 10431 var callArgs = [], argIndex = 0, lastArg, result, len, loop, fn; 10432 len = arr.length; 10433 if (isBoolean(args[0])) { 10434 loop = args[argIndex++]; 10435 } 10436 fn = args[argIndex++]; 10437 lastArg = args[argIndex]; 10438 if (startIndex < 0) { 10439 startIndex += len; 10440 } 10441 startIndex = clampIndex(startIndex, len); 10442 assertArgument(args.length); 10443 fn = wrapper ? wrapper(fn, lastArg) : fn; 10444 callArgs.push(applyCallback(arr, startIndex, fn, lastArg, len, loop)); 10445 if (lastArg) { 10446 callArgs.push(lastArg); 10447 } 10448 result = baseFn.apply(sliceArray(arr, startIndex, loop), callArgs); 10449 if (getResult) { 10450 result = getResult(result, startIndex, len); 10451 } 10452 return result; 10453 }; 10454 } 10455 } 10456 10457 defineInstance(sugarArray, { 10458 10459 /*** 10460 * @method map(map, [context]) 10461 * @returns New Array 10462 * @polyfill ES5 10463 * @short Maps the array to another array whose elements are the values 10464 * returned by the `map` callback. 10465 * @extra [context] is the `this` object. Sugar enhances this method to accept 10466 * a string for `map`, which is a shortcut for a function that gets 10467 * a property or invokes a function on each element. 10468 * Supports `deep properties`. 10469 * 10470 * @callback mapFn 10471 * 10472 * el The element of the current iteration. 10473 * i The index of the current iteration. 10474 * arr A reference to the array. 10475 * 10476 * @example 10477 * 10478 * [1,2,3].map(function(n) { 10479 * return n * 3; 10480 * }); -> [3,6,9] 10481 * 10482 * ['a','aa','aaa'].map('length') -> [1,2,3] 10483 * ['A','B','C'].map('toLowerCase') -> ['a','b','c'] 10484 * users.map('name') -> array of user names 10485 * 10486 * @param {string|mapFn} map 10487 * @param {any} context 10488 * @callbackParam {ArrayElement} el 10489 * @callbackParam {number} i 10490 * @callbackParam {Array} arr 10491 * @callbackReturns {NewArrayElement} mapFn 10492 * 10493 ***/ 10494 'map': fixArgumentLength(enhancedMap), 10495 10496 /*** 10497 * @method some(search, [context]) 10498 * @returns Boolean 10499 * @polyfill ES5 10500 * @short Returns true if `search` is true for any element in the array. 10501 * @extra [context] is the `this` object. Implements `enhanced matching`. 10502 * 10503 * @callback searchFn 10504 * 10505 * el The element of the current iteration. 10506 * i The index of the current iteration. 10507 * arr A reference to the array. 10508 * 10509 * @example 10510 * 10511 * ['a','b','c'].some(function(n) { 10512 * return n == 'a'; 10513 * }); 10514 * ['a','b','c'].some(function(n) { 10515 * return n == 'd'; 10516 * }); 10517 * ['a','b','c'].some('a') -> true 10518 * [{a:2},{b:5}].some({a:2}) -> true 10519 * users.some({ name: /^H/ }) -> true if any have a name starting with H 10520 * 10521 * @param {ArrayElement|searchFn} search 10522 * @param {any} context 10523 * @callbackParam {ArrayElement} el 10524 * @callbackParam {number} i 10525 * @callbackParam {Array} arr 10526 * @callbackReturns {boolean} searchFn 10527 * 10528 ***/ 10529 'some': fixArgumentLength(enhancedSome), 10530 10531 /*** 10532 * @method every(search, [context]) 10533 * @returns Boolean 10534 * @polyfill ES5 10535 * @short Returns true if `search` is true for all elements of the array. 10536 * @extra [context] is the `this` object. Implements `enhanced matching`. 10537 * 10538 * @callback searchFn 10539 * 10540 * el The element of the current iteration. 10541 * i The index of the current iteration. 10542 * arr A reference to the array. 10543 * 10544 * @example 10545 * 10546 * ['a','a','a'].every(function(n) { 10547 * return n == 'a'; 10548 * }); 10549 * ['a','a','a'].every('a') -> true 10550 * [{a:2},{a:2}].every({a:2}) -> true 10551 * users.every({ name: /^H/ }) -> true if all have a name starting with H 10552 * 10553 * @param {ArrayElement|searchFn} search 10554 * @param {any} context 10555 * @callbackParam {ArrayElement} el 10556 * @callbackParam {number} i 10557 * @callbackParam {Array} arr 10558 * @callbackReturns {boolean} searchFn 10559 * 10560 ***/ 10561 'every': fixArgumentLength(enhancedEvery), 10562 10563 /*** 10564 * @method filter(search, [context]) 10565 * @returns Array 10566 * @polyfill ES5 10567 * @short Returns any elements in the array that match `search`. 10568 * @extra [context] is the `this` object. Implements `enhanced matching`. 10569 * 10570 * @callback searchFn 10571 * 10572 * el The element of the current iteration. 10573 * i The index of the current iteration. 10574 * arr A reference to the array. 10575 * 10576 * @example 10577 * 10578 * [1,2,3].filter(function(n) { 10579 * return n > 1;
10580 * }); 10581 * [1,2,2,4].filter(2) -> 2 10582 * users.filter({ name: /^H/ }) -> all users with a name starting with H 10583 * 10584 * @param {ArrayElement|searchFn} search 10585 * @param {any} context 10586 * @callbackParam {ArrayElement} el 10587 * @callbackParam {number} i 10588 * @callbackParam {Array} arr 10589 * @callbackReturns {boolean} searchFn 10590 * 10591 ***/ 10592 'filter': fixArgumentLength(enhancedFilter), 10593 10594 /*** 10595 * @method find(search, [context]) 10596 * @returns Mixed 10597 * @polyfill ES6 10598 * @short Returns the first element in the array that matches `search`. 10599 * @extra Implements `enhanced matching`. 10600 * 10601 * @callback searchFn 10602 * 10603 * el The element of the current iteration. 10604 * i The index of the current iteration. 10605 * arr A reference to the array. 10606 * 10607 * @example 10608 * 10609 * users.find(function(user) { 10610 * return user.name = 'Harry'; 10611 * }); -> harry! 10612 * 10613 * users.find({ name: 'Harry' }); -> harry! 10614 * users.find({ name: /^[A-H]/ }); -> First user with name starting with A-H 10615 * users.find({ titles: ['Ms', 'Dr'] }); -> not harry! 10616 * 10617 * @param {ArrayElement|searchFn} search 10618 * @param {any} context 10619 * @callbackParam {ArrayElement} el 10620 * @callbackParam {number} i 10621 * @callbackParam {Array} arr 10622 * @callbackReturns {boolean} searchFn 10623 * 10624 ***/ 10625 'find': fixArgumentLength(enhancedFind), 10626 10627 /*** 10628 * @method findIndex(search, [context]) 10629 * @returns Number 10630 * @polyfill ES6 10631 * @short Returns the index of the first element in the array that matches 10632 * `search`, or `-1` if none. 10633 * @extra [context] is the `this` object. Implements `enhanced matching`. 10634 * 10635 * @callback searchFn 10636 * 10637 * el The element of the current iteration. 10638 * i The index of the current iteration. 10639 * arr A reference to the array. 10640 * 10641 * @example 10642 * 10643 * [1,2,3,4].findIndex(function(n) { 10644 * return n % 2 == 0; 10645 * }); -> 1 10646 * ['a','b','c'].findIndex('c'); -> 2 10647 * ['cuba','japan','canada'].find(/^c/) -> 0 10648 * 10649 * @param {ArrayElement|searchFn} search 10650 * @param {any} context 10651 * @callbackParam {ArrayElement} el 10652 * @callbackParam {number} i 10653 * @callbackParam {Array} arr 10654 * @callbackReturns {boolean} searchFn 10655 * 10656 ***/ 10657 'findIndex': fixArgumentLength(enhancedFindIndex) 10658 10659 }, [ENHANCEMENTS_FLAG, ARRAY_ENHANCEMENTS_FLAG]); 10660 10661 10662 defineInstance(sugarArray, { 10663 10664 /*** 10665 * @method none(search, [context]) 10666 * 10667 * @returns Boolean 10668 * @short Returns true if none of the elements in the array match `search`. 10669 * @extra [context] is the `this` object. Implements `enhanced matching`. 10670 * 10671 * @callback searchFn 10672 * 10673 * el The element of the current iteration. 10674 * i The index of the current iteration. 10675 * arr A reference to the array. 10676 * 10677 * @example 10678 * 10679 * [1,2,3].none(5) -> true 10680 * ['a','b','c'].none(/b/) -> false 10681 * users.none(function(user) { 10682 * return user.name == 'Wolverine'; 10683 * }); -> probably true 10684 * users.none({ name: 'Wolverine' }); -> same as above 10685 * 10686 * @param {ArrayElement|searchFn} search 10687 * @param {any} context 10688 * @callbackParam {ArrayElement} el 10689 * @callbackParam {number} i 10690 * @callbackParam {Array} arr 10691 * @callbackReturns {boolean} searchFn 10692 * 10693 ***/ 10694 'none': fixArgumentLength(arrayNone), 10695 10696 /*** 10697 * @method count(search, [context]) 10698 * @returns Number 10699 * @short Counts all elements in the array that match `search`. 10700 * @extra Implements `enhanced matching`. 10701 * 10702 * @callback searchFn 10703 * 10704 * el The element of the current iteration. 10705 * i The index of the current iteration. 10706 * arr A reference to the array. 10707 * 10708 * @example 10709 * 10710 * ['a','b','a'].count('a') -> 2 10711 * ['a','b','c'].count(/b/) -> 1 10712 * users.count(function(user) { 10713 * return user.age > 30; 10714 * }); -> number of users older than 30 10715 * 10716 * @param {ArrayElement|searchFn} search 10717 * @param {any} context 10718 * @callbackParam {ArrayElement} el 10719 * @callbackParam {number} i 10720 * @callbackParam {Array} arr 10721 * @callbackReturns {boolean} searchFn 10722 * 10723 ***/ 10724 'count': fixArgumentLength(arrayCount), 10725 10726 /*** 10727 * @method min([all] = false, [map]) 10728 * @returns Mixed 10729 * @short Returns the element in the array with the lowest value. 10730 * @extra [map] may be passed in place of [all], and is a function mapping the 10731 * value to be checked or a string acting as a shortcut. If [all] is 10732 * true, multiple elements will be returned. Supports `deep properties`. 10733 * 10734 * @callback mapFn 10735 * 10736 * el The element of the current iteration. 10737 * i The index of the current iteration. 10738 * arr A reference to the array. 10739 * 10740 * @example 10741 * 10742 * [1,2,3].min() -> 1 10743 * ['fee','fo','fum'].min('length') -> 'fo' 10744 * ['fee','fo','fum'].min(true, 'length') -> ['fo'] 10745 * users.min('age') -> youngest guy! 10746 * 10747 * ['fee','fo','fum'].min(true, function(n) { 10748 * return n.length; 10749 * }); -> ['fo'] 10750 * 10751 * @signature min([map]) 10752 * @param {string|mapFn} map 10753 * @param {boolean} all 10754 * @callbackParam {ArrayElement} el 10755 * @callbackParam {number} i 10756 * @callbackParam {Array} arr 10757 * @callbackReturns {NewArrayElement} mapFn 10758 * 10759 ***/ 10760 'min': function(arr, all, map) { 10761 return getMinOrMax(arr, all, map); 10762 }, 10763 10764 /*** 10765 * @method max([all] = false, [map]) 10766 * @returns Mixed 10767 * @short Returns the element in the array with the greatest value. 10768 * @extra [map] may be passed in place of [all], and is a function mapping the 10769 * value to be checked or a string acting as a shortcut. If [all] is 10770 * true, multiple elements will be returned. Supports `deep properties`. 10771 * 10772 * @callback mapFn 10773 * 10774 * el The element of the current iteration. 10775 * i The index of the current iteration. 10776 * arr A reference to the array. 10777 * 10778 * @example 10779 * 10780 * [1,2,3].max() -> 3 10781 * ['fee','fo','fum'].max('length') -> 'fee' 10782 * ['fee','fo','fum'].max(true, 'length') -> ['fee','fum'] 10783 * users.max('age') -> oldest guy! 10784 * 10785 * ['fee','fo','fum'].max(true, function(n) { 10786 * return n.length; 10787 * }); -> ['fee', 'fum'] 10788 * 10789 * @signature max([map]) 10790 * @param {string|mapFn} map 10791 * @param {boolean} all 10792 * @callbackParam {ArrayElement} el 10793 * @callbackParam {number} i 10794 * @callbackParam {Array} arr 10795 * @callbackReturns {NewArrayElement} mapFn 10796 * 10797 ***/ 10798 'max': function(arr, all, map) { 10799 return getMinOrMax(arr, all, map, true); 10800 }, 10801 10802 /*** 10803 * @method least([all] = false, [map]) 10804 * @returns Array 10805 * @short Returns the elements in the array with the least commonly occuring value. 10806 * @extra [map] may be passed in place of [all], and is a function mapping the 10807 * value to be checked or a string acting as a shortcut. If [all] is 10808 * true, will return multiple values in an array. 10809 * Supports `deep properties`. 10810 * 10811 * @callback mapFn 10812 * 10813 * el The element of the current iteration. 10814 * i The index of the current iteration. 10815 * arr A reference to the array. 10816 * 10817 * @example 10818 *
10819 * [3,2,2].least() -> 3 10820 * ['fe','fo','fum'].least(true, 'length') -> ['fum'] 10821 * users.least('profile.type') -> (user with least commonly occurring type) 10822 * users.least(true, 'profile.type') -> (users with least commonly occurring type) 10823 * 10824 * @signature least([map]) 10825 * @param {string|mapFn} map 10826 * @param {boolean} all 10827 * @callbackParam {ArrayElement} el 10828 * @callbackParam {number} i 10829 * @callbackParam {Array} arr 10830 * @callbackReturns {NewArrayElement} mapFn 10831 * 10832 ***/ 10833 'least': function(arr, all, map) { 10834 return getLeastOrMost(arr, all, map); 10835 }, 10836 10837 /*** 10838 * @method most([all] = false, [map]) 10839 * @returns Array 10840 * @short Returns the elements in the array with the most commonly occuring value. 10841 * @extra [map] may be passed in place of [all], and is a function mapping the 10842 * value to be checked or a string acting as a shortcut. If [all] is 10843 * true, will return multiple values in an array. 10844 * Supports `deep properties`. 10845 * 10846 * @callback mapFn 10847 * 10848 * el The element of the current iteration. 10849 * i The index of the current iteration. 10850 * arr A reference to the array. 10851 * 10852 * @example 10853 * 10854 * [3,2,2].most(2) -> 2 10855 * ['fe','fo','fum'].most(true, 'length') -> ['fe','fo'] 10856 * users.most('profile.type') -> (user with most commonly occurring type) 10857 * users.most(true, 'profile.type') -> (users with most commonly occurring type) 10858 * 10859 * @signature most([map]) 10860 * @param {string|mapFn} map 10861 * @param {boolean} all 10862 * @callbackParam {ArrayElement} el 10863 * @callbackParam {number} i 10864 * @callbackParam {Array} arr 10865 * @callbackReturns {NewArrayElement} mapFn 10866 * 10867 ***/ 10868 'most': function(arr, all, map) { 10869 return getLeastOrMost(arr, all, map, true); 10870 }, 10871 10872 /*** 10873 * @method sum([map]) 10874 * @returns Number 10875 * @short Sums all values in the array. 10876 * @extra [map] may be a function mapping the value to be summed or a string 10877 * acting as a shortcut. 10878 * 10879 * @callback mapFn 10880 * 10881 * el The element of the current iteration. 10882 * i The index of the current iteration. 10883 * arr A reference to the array. 10884 * 10885 * @example 10886 * 10887 * [1,2,2].sum() -> 5 10888 * users.sum(function(user) { 10889 * return user.votes; 10890 * }); -> total votes! 10891 * users.sum('votes') -> total votes! 10892 * 10893 * @param {string|mapFn} map 10894 * @callbackParam {ArrayElement} el 10895 * @callbackParam {number} i 10896 * @callbackParam {Array} arr 10897 * @callbackReturns {NewArrayElement} mapFn 10898 * 10899 ***/ 10900 'sum': function(arr, map) { 10901 return sum(arr, map); 10902 }, 10903 10904 /*** 10905 * @method average([map]) 10906 * @returns Number 10907 * @short Gets the mean average for all values in the array. 10908 * @extra [map] may be a function mapping the value to be averaged or a string 10909 * acting as a shortcut. Supports `deep properties`. 10910 * 10911 * @callback mapFn 10912 * 10913 * el The element of the current iteration. 10914 * i The index of the current iteration. 10915 * arr A reference to the array. 10916 * 10917 * @example 10918 * 10919 * [1,2,3,4].average() -> 2 10920 * users.average(function(user) { 10921 * return user.age; 10922 * }); -> average user age 10923 * users.average('age') -> average user age 10924 * users.average('currencies.usd.balance') -> average USD balance 10925 * 10926 * @param {string|mapFn} map 10927 * @callbackParam {ArrayElement} el 10928 * @callbackParam {number} i 10929 * @callbackParam {Array} arr 10930 * @callbackReturns {NewArrayElement} mapFn 10931 * 10932 ***/ 10933 'average': function(arr, map) { 10934 return average(arr, map); 10935 }, 10936 10937 /*** 10938 * @method median([map]) 10939 * @returns Number 10940 * @short Gets the median average for all values in the array. 10941 * @extra [map] may be a function mapping the value to be averaged or a string 10942 * acting as a shortcut. 10943 * 10944 * @callback mapFn 10945 * 10946 * el The element of the current iteration. 10947 * i The index of the current iteration. 10948 * arr A reference to the array. 10949 * 10950 * @example 10951 * 10952 * [1,2,2].median() -> 2 10953 * [{a:1},{a:2},{a:2}].median('a') -> 2 10954 * users.median('age') -> median user age 10955 * users.median('currencies.usd.balance') -> median USD balance 10956 * 10957 * @param {string|mapFn} map 10958 * @callbackParam {ArrayElement} el 10959 * @callbackParam {number} i 10960 * @callbackParam {Array} arr 10961 * @callbackReturns {NewArrayElement} mapFn 10962 * 10963 ***/ 10964 'median': function(arr, map) { 10965 return median(arr, map); 10966 } 10967 10968 });
10969 10970 10971 /*** @namespace Object ***/ 10972 10973 // Object matchers 10974 var objectSome = wrapObjectMatcher('some'), 10975 objectFind = wrapObjectMatcher('find'), 10976 objectEvery = wrapObjectMatcher('every'); 10977 10978 function objectForEach(obj, fn) { 10979 assertCallable(fn); 10980 forEachProperty(obj, function(val, key) { 10981 fn(val, key, obj); 10982 }); 10983 return obj; 10984 } 10985 10986 function objectMap(obj, map) { 10987 var result = {}; 10988 forEachProperty(obj, function(val, key) { 10989 result[key] = mapWithShortcuts(val, map, obj, [val, key, obj]); 10990 }); 10991 return result; 10992 } 10993 10994 function objectReduce(obj, fn, acc) { 10995 var init = isDefined(acc); 10996 forEachProperty(obj, function(val, key) { 10997 if (!init) { 10998 acc = val; 10999 init = true; 11000 return; 11001 } 11002 acc = fn(acc, val, key, obj); 11003 }); 11004 return acc; 11005 } 11006 11007 function objectNone(obj, f) { 11008 return !objectSome(obj, f); 11009 } 11010 11011 function objectFilter(obj, f) { 11012 var matcher = getMatcher(f), result = {}; 11013 forEachProperty(obj, function(val, key) { 11014 if (matcher(val, key, obj)) { 11015 result[key] = val; 11016 } 11017 }); 11018 return result; 11019 } 11020 11021 function objectCount(obj, f) { 11022 var matcher = getMatcher(f), count = 0; 11023 forEachProperty(obj, function(val, key) { 11024 if (matcher(val, key, obj)) { 11025 count++; 11026 } 11027 }); 11028 return count; 11029 } 11030 11031 // Support 11032 11033 function wrapObjectMatcher(name) { 11034 var nativeFn = Array.prototype[name]; 11035 return function(obj, f) { 11036 var matcher = getMatcher(f); 11037 return nativeFn.call(getKeys(obj), function(key) { 11038 return matcher(obj[key], key, obj); 11039 }); 11040 }; 11041 } 11042 11043 defineInstanceAndStatic(sugarObject, { 11044 11045 /*** 11046 * @method forEach(fn) 11047 * @returns Object 11048 * @short Runs `fn` against each property in the object. 11049 * @extra Does not iterate over inherited or non-enumerable properties. 11050 * 11051 * @callback eachFn 11052 * 11053 * val The value of the current iteration. 11054 * key The key of the current iteration. 11055 * obj A reference to the object. 11056 * 11057 * @example 11058 * 11059 * Object.forEach({a:'b'}, function(val, key) { 11060 * // val = 'b', key = a 11061 * }); 11062 * 11063 * @param {eachFn} fn 11064 * @callbackParam {Property} val 11065 * @callbackParam {string} key 11066 * @callbackParam {Object} obj 11067 * 11068 ***/ 11069 'forEach': function(obj, fn) { 11070 return objectForEach(obj, fn); 11071 }, 11072 11073 /*** 11074 * @method map(map) 11075 * @returns Object 11076 * @short Maps the object to another object whose properties are the values 11077 * returned by `map`. 11078 * @extra `map` can also be a string, which is a shortcut for a function that 11079 * gets that property (or invokes a function) on each element. 11080 * Supports `deep properties`. 11081 * 11082 * @callback mapFn 11083 * 11084 * val The value of the current property. 11085 * key The key of the current property. 11086 * obj A reference to the object. 11087 * 11088 * @example 11089 * 11090 * data.map(function(val, key) { 11091 * return key; 11092 * }); -> {a:'b'} 11093 * users.map('age'); 11094 * 11095 * @param {string|mapFn} map 11096 * @callbackParam {Property} val 11097 * @callbackParam {string} key 11098 * @callbackParam {Object} obj 11099 * @callbackReturns {NewProperty} mapFn 11100 * 11101 ***/ 11102 'map': function(obj, map) { 11103 return objectMap(obj, map); 11104 }, 11105 11106 /*** 11107 * @method some(search) 11108 * @returns Boolean 11109 * @short Returns true if `search` is true for any property in the object. 11110 * @extra Implements `enhanced matching`. 11111 * 11112 * @callback searchFn 11113 * 11114 * val The value of the current iteration. 11115 * key The key of the current iteration. 11116 * obj A reference to the object. 11117 * 11118 * @example 11119 * 11120 * Object.some({a:1,b:2}, function(val) { 11121 * return val == 1;
11122 * }); -> true 11123 * Object.some({a:1,b:2}, 1); -> true 11124 * 11125 * @param {Property|searchFn} search 11126 * @callbackParam {Property} val 11127 * @callbackParam {string} key 11128 * @callbackParam {Object} obj 11129 * @callbackReturns {boolean} searchFn 11130 * 11131 ***/ 11132 'some': objectSome, 11133 11134 /*** 11135 * @method every(search) 11136 * @returns Boolean 11137 * @short Returns true if `search` is true for all properties in the object. 11138 * @extra Implements `enhanced matching`. 11139 * 11140 * @callback searchFn 11141 * 11142 * val The value of the current iteration. 11143 * key The key of the current iteration. 11144 * obj A reference to the object. 11145 * 11146 * @example 11147 * 11148 * Object.every({a:1,b:2}, function(val) { 11149 * return val > 0; 11150 * }); -> true 11151 * Object.every({a:'a',b:'b'}, /[a-z]/); -> true 11152 * 11153 * @param {Property|searchFn} search 11154 * @callbackParam {Property} val 11155 * @callbackParam {string} key 11156 * @callbackParam {Object} obj 11157 * @callbackReturns {boolean} searchFn 11158 * 11159 ***/ 11160 'every': objectEvery, 11161 11162 /*** 11163 * @method filter(search) 11164 * @returns Array 11165 * @short Returns a new object with properties that match `search`. 11166 * @extra Implements `enhanced matching`. 11167 * 11168 * @callback searchFn 11169 * 11170 * val The value of the current iteration. 11171 * key The key of the current iteration. 11172 * obj A reference to the object. 11173 * 11174 * @example 11175 * 11176 * Object.filter({a:1,b:2}, function(val) { 11177 * return val == 1; 11178 * }); -> {a:1} 11179 * Object.filter({a:'a',z:'z'}, /[a-f]/); -> {a:'a'} 11180 * Object.filter(usersByName, /^H/); -> all users with names starting with H 11181 * 11182 * @param {Property|searchFn} search 11183 * @callbackParam {Property} val 11184 * @callbackParam {string} key 11185 * @callbackParam {Object} obj 11186 * @callbackReturns {boolean} searchFn 11187 * 11188 ***/ 11189 'filter': function(obj, f) { 11190 return objectFilter(obj, f); 11191 }, 11192 11193 /*** 11194 * @method reduce(reduceFn, [init]) 11195 * @returns Mixed 11196 * @short Reduces the object to a single result. 11197 * @extra This operation is sometimes called "accumulation", as it takes the 11198 * result of the last iteration of `fn` and passes it as the first 11199 * argument to the next iteration, "accumulating" that value as it goes. 11200 * The return value of this method will be the return value of the final 11201 * iteration of `fn`. If [init] is passed, it will be the initial 11202 * "accumulator" (the first argument). If [init] is not passed, then a 11203 * property of the object will be used instead and `fn` will not be 11204 * called for that property. Note that object properties have no order, 11205 * and this may lead to bugs (for example if performing division or 11206 * subtraction operations on a value). If order is important, use an 11207 * array instead! 11208 * 11209 * @callback reduceFn 11210 * 11211 * acc The "accumulator", either [init], the result of the last iteration 11212 * of `fn`, or a property of `obj`. 11213 * val The value of the current property called for `fn`. 11214 * key The key of the current property called for `fn`. 11215 * obj A reference to the object. 11216 * 11217 * @example 11218 * 11219 * Object.reduce({a:2,b:4}, function(a, b) { 11220 * return a * b; 11221 * }); -> 8 11222 * 11223 * Object.reduce({a:2,b:4}, function(a, b) { 11224 * return a * b; 11225 * }, 10); -> 80 11226 * 11227 * 11228 * @param {reduceFn} reduceFn 11229 * @param {any} [init] 11230 * @callbackParam {Property} acc 11231 * @callbackParam {Property} val 11232 * @callbackParam {string} key 11233 * @callbackParam {Object} obj 11234 * 11235 ***/ 11236 'reduce': function(obj, fn, init) { 11237 return objectReduce(obj, fn, init); 11238 }, 11239 11240 /*** 11241 * @method find(search) 11242 * @returns Boolean 11243 * @short Returns the first key whose value matches `search`. 11244 * @extra Implements `enhanced matching`. Note that "first" is 11245 * implementation-dependent. If order is important an array should be 11246 * used instead. 11247 * 11248 * @callback searchFn 11249 * 11250 * val The value of the current iteration. 11251 * key The key of the current iteration. 11252 * obj A reference to the object. 11253 * 11254 * @example 11255 * 11256 * Object.find({a:1,b:2}, function(val) { 11257 * return val == 2; 11258 * }); -> 'b' 11259 * Object.find({a:'a',b:'b'}, /[a-z]/); -> 'a' 11260 * 11261 * @param {Property|searchFn} search 11262 * @callbackParam {Property} val 11263 * @callbackParam {string} key 11264 * @callbackParam {Object} obj 11265 * @callbackReturns {boolean} searchFn 11266 * 11267 ***/ 11268 'find': objectFind, 11269 11270 /*** 11271 * @method count(search) 11272 * @returns Number 11273 * @short Counts all properties in the object that match `search`. 11274 * @extra Implements `enhanced matching`. 11275 * 11276 * @callback searchFn 11277 * 11278 * val The value of the current iteration. 11279 * key The key of the current iteration. 11280 * obj A reference to the object. 11281 * 11282 * @example 11283 * 11284 * Object.count({a:'a',b:'b',c:'a'}, 'a') -> 2 11285 * Object.count(usersByName, function(user) { 11286 * return user.age > 30; 11287 * }); -> number of users older than 30 11288 * Object.count(usersByName, { name: /^[H-Z]/ }); 11289 * 11290 * @param {Property|searchFn} search 11291 * @callbackParam {Property} val 11292 * @callbackParam {string} key 11293 * @callbackParam {Object} obj 11294 * @callbackReturns {boolean} searchFn 11295 * 11296 ***/ 11297 'count': function(obj, f) { 11298 return objectCount(obj, f); 11299 }, 11300 11301 /*** 11302 * @method none(search) 11303 * @returns Boolean 11304 * @short Returns true if none of the properties in the object match `search`. 11305 * @extra Implements `enhanced matching`. 11306 * 11307 * @callback searchFn 11308 * 11309 * val The value of the current iteration. 11310 * key The key of the current iteration. 11311 * obj A reference to the object. 11312 * 11313 * @example 11314 * 11315 * Object.none({a:1,b:2}, 3); -> true 11316 * Object.none(usersByName, function(user) { 11317 * return user.name == 'Wolverine'; 11318 * }); -> probably true 11319 * 11320 * @param {Property|searchFn} search 11321 * @callbackParam {Property} val 11322 * @callbackParam {string} key 11323 * @callbackParam {Object} obj 11324 * @callbackReturns {boolean} searchFn 11325 * 11326 ***/ 11327 'none': function(obj, f) { 11328 return objectNone(obj, f); 11329 }, 11330 11331 /*** 11332 * @method sum([map]) 11333 * @returns Number 11334 * @short Sums all properties in the object. 11335 * @extra [map] may be a function mapping the value to be summed or a string 11336 * acting as a shortcut. 11337 * 11338 * @callback mapFn 11339 * 11340 * val The value of the current iteration. 11341 * key The key of the current iteration. 11342 * obj A reference to the object. 11343 * 11344 * @example 11345 * 11346 * Object.sum({a:35,b:13}); -> 48 11347 * Object.sum(usersByName, function(user) { 11348 * return user.votes; 11349 * }); -> total user votes 11350 * 11351 * @param {string|mapFn} map 11352 * @callbackParam {Property} val 11353 * @callbackParam {string} key 11354 * @callbackParam {Object} obj 11355 * @callbackReturns {NewProperty} mapFn 11356 * 11357 ***/ 11358 'sum': function(obj, map) { 11359 return sum(obj, map); 11360 }, 11361 11362 /*** 11363 * @method average([map]) 11364 * @returns Number 11365 * @short Gets the mean average of all properties in the object. 11366 * @extra [map] may be a function mapping the value to be averaged or a string 11367 * acting as a shortcut. 11368 * 11369 * @callback mapFn 11370 * 11371 * val The value of the current iteration. 11372 * key The key of the current iteration. 11373 * obj A reference to the object. 11374 * 11375 * @example 11376 * 11377 * Object.average({a:35,b:11}); -> 23 11378 * Object.average(usersByName, 'age'); -> average user age 11379 * Object.average(usersByName, 'currencies.usd.balance'); -> USD mean balance 11380 * 11381 * @param {string|mapFn} map 11382 * @callbackParam {Property} val 11383 * @callbackParam {string} key 11384 * @callbackParam {Object} obj 11385 * @callbackReturns {NewProperty} mapFn 11386 * 11387 ***/ 11388 'average': function(obj, map) { 11389 return average(obj, map); 11390 }, 11391 11392 /*** 11393 * @method median([map]) 11394 * @returns Number 11395 * @short Gets the median average of all properties in the object. 11396 * @extra [map] may be a function mapping the value to be averaged or a string 11397 * acting as a shortcut. 11398 * 11399 * @callback mapFn 11400 * 11401 * val The value of the current iteration. 11402 * key The key of the current iteration. 11403 * obj A reference to the object. 11404 * 11405 * @example 11406 * 11407 * Object.median({a:1,b:2,c:2}) -> 2 11408 * Object.median(usersByName, 'age'); -> median user age 11409 * Object.median(usersByName, 'currencies.usd.balance'); -> USD median balance 11410 * 11411 * @param {string|mapFn} map 11412 * @callbackParam {Property} val 11413 * @callbackParam {string} key 11414 * @callbackParam {Object} obj 11415 * @callbackReturns {NewProperty} mapFn 11416 * 11417 ***/ 11418 'median': function(obj, map) { 11419 return median(obj, map); 11420 }, 11421 11422 /*** 11423 * @method min([all] = false, [map]) 11424 * @returns Mixed 11425 * @short Returns the key of the property in the object with the lowest value. 11426 * @extra If [all] is true, will return an object with all properties in the 11427 * object with the lowest value. [map] may be passed in place of [all] 11428 * and is a function mapping the value to be checked or a string acting 11429 * as a shortcut. 11430 * 11431 * @callback mapFn 11432 * 11433 * val The value of the current iteration. 11434 * key The key of the current iteration. 11435 * obj A reference to the object. 11436 * 11437 * @example 11438 * 11439 * Object.min({a:1,b:2,c:3}) -> 'a' 11440 * Object.min({a:'aaa',b:'bb',c:'c'}, 'length') -> 'c' 11441 * Object.min({a:1,b:1,c:3}, true) -> {a:1,b:1} 11442 * 11443 * @signature min([map]) 11444 * @param {string|mapFn} map 11445 * @param {boolean} [all] 11446 * @callbackParam {Property} val 11447 * @callbackParam {string} key 11448 * @callbackParam {Object} obj 11449 * @callbackReturns {NewProperty} mapFn 11450 * 11451 ***/ 11452 'min': function(obj, all, map) { 11453 return getMinOrMax(obj, all, map, false, true); 11454 }, 11455 11456 /*** 11457 * @method max([all] = false, [map]) 11458 * @returns Mixed 11459 * @short Returns the key of the property in the object with the highest value. 11460 * @extra If [all] is true, will return an object with all properties in the 11461 * object with the highest value. [map] may be passed in place of [all] 11462 * and is a function mapping the value to be checked or a string acting 11463 * as a shortcut. 11464 * 11465 * @callback mapFn 11466 * 11467 * val The value of the current iteration. 11468 * key The key of the current iteration. 11469 * obj A reference to the object. 11470 * 11471 * @example 11472 * 11473 * Object.max({a:1,b:2,c:3}) -> 'c' 11474 * Object.max({a:'aaa',b:'bb',c:'c'}, 'length') -> 'a' 11475 * Object.max({a:1,b:3,c:3}, true) -> {b:3,c:3} 11476 * 11477 * @signature max([map]) 11478 * @param {string|mapFn} map 11479 * @param {boolean} [all] 11480 * @callbackParam {Property} val 11481 * @callbackParam {string} key 11482 * @callbackParam {Object} obj 11483 * @callbackReturns {NewProperty} mapFn 11484 * 11485 ***/ 11486 'max': function(obj, all, map) { 11487 return getMinOrMax(obj, all, map, true, true); 11488 }, 11489 11490 /*** 11491 * @method least([all] = false, [map]) 11492 * @returns Mixed 11493 * @short Returns the key of the property in the object with the least commonly 11494 * occuring value. 11495 * @extra If [all] is true, will return an object with all properties in the 11496 * object with the least common value. [map] may be passed in place of 11497 * [all] and is a function mapping the value to be checked or a string 11498 * acting as a shortcut. 11499 * 11500 * @callback mapFn 11501 * 11502 * val The value of the current iteration. 11503 * key The key of the current iteration. 11504 * obj A reference to the object. 11505 * 11506 * @example 11507 * 11508 * Object.least({a:1,b:3,c:3}) -> 'a' 11509 * Object.least({a:'aa',b:'bb',c:'c'}, 'length') -> 'c' 11510 * Object.least({a:1,b:3,c:3}, true) -> {a:1} 11511 * 11512 * @signature least([map]) 11513 * @param {string|mapFn} map 11514 * @param {boolean} [all] 11515 * @callbackParam {Property} val 11516 * @callbackParam {string} key 11517 * @callbackParam {Object} obj 11518 * @callbackReturns {NewProperty} mapFn 11519 * 11520 ***/ 11521 'least': function(obj, all, map) { 11522 return getLeastOrMost(obj, all, map, false, true); 11523 }, 11524 11525 /*** 11526 * @method most([all] = false, [map]) 11527 * @returns Mixed 11528 * @short Returns the key of the property in the object with the most commonly 11529 * occuring value. 11530 * @extra If [all] is true, will return an object with all properties in the 11531 * object with the most common value. [map] may be passed in place of 11532 * [all] and is a function mapping the value to be checked or a string 11533 * acting as a shortcut. 11534 * 11535 * @callback mapFn 11536 * 11537 * val The value of the current iteration. 11538 * key The key of the current iteration. 11539 * obj A reference to the object. 11540 * 11541 * @example 11542 * 11543 * Object.most({a:1,b:3,c:3}) -> 'b' 11544 * Object.most({a:'aa',b:'bb',c:'c'}, 'length') -> 'a' 11545 * Object.most({a:1,b:3,c:3}, true) -> {b:3,c:3} 11546 * 11547 * @signature most([map]) 11548 * @param {string|mapFn} map 11549 * @param {boolean} [all] 11550 * @callbackParam {Property} val 11551 * @callbackParam {string} key 11552 * @callbackParam {Object} obj 11553 * @callbackReturns {NewProperty} mapFn 11554 * 11555 ***/ 11556 'most': function(obj, all, map) { 11557 return getLeastOrMost(obj, all, map, true, true); 11558 } 11559 11560 });
11561 11562 11563 buildFromIndexMethods(); 11564 11565 /*** 11566 * @module Number 11567 * @description Number formatting, precision rounding, Math aliases, and more. 11568 * 11569 ***/ 11570 11571 11572 var NUMBER_OPTIONS = { 11573 'decimal': HALF_WIDTH_PERIOD, 11574 'thousands': HALF_WIDTH_COMMA 11575 }; 11576 11577 // Abbreviation Units 11578 var BASIC_UNITS = '|kmbt', 11579 MEMORY_UNITS = '|KMGTPE', 11580 MEMORY_BINARY_UNITS = '|,Ki,Mi,Gi,Ti,Pi,Ei', 11581 METRIC_UNITS_SHORT = 'nμm|k', 11582 METRIC_UNITS_FULL = 'yzafpnμm|KMGTPEZY'; 11583 11584 11585 /*** 11586 * @method getOption(name) 11587 * @returns Mixed 11588 * @accessor 11589 * @short Gets an option used interally by Number. 11590 * @example 11591 * 11592 * Sugar.Number.getOption('thousands'); 11593 * 11594 * @param {string} name 11595 * 11596 *** 11597 * @method setOption(name, value) 11598 * @accessor 11599 * @short Sets an option used interally by Number. 11600 * @extra If `value` is `null`, the default value will be restored. 11601 * @options 11602 * 11603 * decimal A string used as the decimal marker by `format`, `abbr`, 11604 * `metric`, and `bytes`. Default is `.`. 11605 * 11606 * thousands A string used as the thousands marker by `format`, `abbr`, 11607 * `metric`, and `bytes`. Default is `,`. 11608 * 11609 * 11610 * @example 11611 * 11612 * Sugar.Number.setOption('decimal', ','); 11613 * Sugar.Number.setOption('thousands', ' '); 11614 * 11615 * @signature setOption(options) 11616 * @param {NumberOptions} options 11617 * @param {string} name 11618 * @param {any} value 11619 * @option {string} decimal 11620 * @option {string} thousands 11621 * 11622 ***/ 11623 var _numberOptions = defineOptionsAccessor(sugarNumber, NUMBER_OPTIONS); 11624 11625 11626 function abbreviateNumber(num, precision, ustr, bytes) { 11627 var fixed = num.toFixed(20), 11628 decimalPlace = fixed.search(/\./), 11629 numeralPlace = fixed.search(/[1-9]/), 11630 significant = decimalPlace - numeralPlace, 11631 units, unit, mid, i, divisor; 11632 if (significant > 0) { 11633 significant -= 1; 11634 } 11635 units = commaSplit(ustr); 11636 if (units.length === 1) { 11637 units = ustr.split(''); 11638 } 11639 mid = units.indexOf('|'); 11640 if (mid === -1) { 11641 // Skipping the placeholder means the units should start from zero, 11642 // otherwise assume they end at zero. 11643 mid = units[0] === '_' ? 0 : units.length; 11644 } 11645 i = max(min(floor(significant / 3), units.length - mid - 1), -mid); 11646 unit = units[i + mid]; 11647 while (unit === '_') { 11648 i += i < 0 ? -1 : 1; 11649 unit = units[i + mid]; 11650 } 11651 if (unit === '|') { 11652 unit = ''; 11653 } 11654 if (significant < -9) { 11655 precision = abs(significant) - 9; 11656 } 11657 divisor = bytes ? pow(2, 10 * i) : pow(10, i * 3); 11658 return numberFormat(withPrecision(num / divisor, precision || 0)) + unit; 11659 } 11660 11661 function numberFormat(num, place) { 11662 var result = '', thousands, decimal, fraction, integer, split, str; 11663 11664 decimal = _numberOptions('decimal'); 11665 thousands = _numberOptions('thousands'); 11666 11667 if (isNumber(place)) { 11668 str = withPrecision(num, place || 0).toFixed(max(place, 0)); 11669 } else { 11670 str = num.toString(); 11671 } 11672 11673 str = str.replace(/^-/, ''); 11674 split = periodSplit(str); 11675 integer = split[0]; 11676 fraction = split[1]; 11677 if (/e/.test(str)) { 11678 result = str; 11679 } else { 11680 for (var i = integer.length; i > 0; i -= 3) { 11681 if (i < integer.length) { 11682 result = thousands + result; 11683 } 11684 result = integer.slice(max(0, i - 3), i) + result; 11685 } 11686 } 11687 if (fraction) { 11688 result += decimal + repeatString('0', (place || 0) - fraction.length) + fraction; 11689 } 11690 return (num < 0 ? '-' : '') + result; 11691 } 11692 11693 function isInteger(n) { 11694 return n % 1 === 0; 11695 } 11696 11697 function isMultipleOf(n1, n2) { 11698 return n1 % n2 === 0; 11699 } 11700 11701 function createRoundingFunction(fn) { 11702 return function(n, precision) { 11703 return precision ? withPrecision(n, precision, fn) : fn(n); 11704 }; 11705 } 11706 11707 defineStatic(sugarNumber, { 11708 11709 /*** 11710 * @method random([n1], [n2]) 11711 * @returns Number 11712 * @static 11713 * @short Returns a random integer from [n1] to [n2] (both inclusive). 11714 * @extra If only 1 number is passed, the other will be 0. If none are passed, 11715 * the number will be either 0 or 1. 11716 * 11717 * @example 11718 * 11719 * Number.random(50, 100) -> ex. 85 11720 * Number.random(50) -> ex. 27 11721 * Number.random() -> ex. 0 11722 * 11723 * @param {number} [n1] 11724 * @param {number} [n2] 11725 * 11726 ***/ 11727 'random': function(n1, n2) { 11728 var minNum, maxNum; 11729 if (arguments.length == 1) n2 = n1, n1 = 0; 11730 minNum = min(n1 || 0, isUndefined(n2) ? 1 : n2); 11731 maxNum = max(n1 || 0, isUndefined(n2) ? 1 : n2) + 1; 11732 return trunc((Math.random() * (maxNum - minNum)) + minNum); 11733 } 11734 11735 });
11736 11737 defineInstance(sugarNumber, { 11738 11739 /*** 11740 * @method isInteger() 11741 * @returns Boolean 11742 * @short Returns true if the number has no trailing decimal. 11743 * 11744 * @example 11745 * 11746 * (420).isInteger() -> true 11747 * (4.5).isInteger() -> false 11748 * 11749 ***/ 11750 'isInteger': function(n) { 11751 return isInteger(n); 11752 }, 11753 11754 /*** 11755 * @method isOdd() 11756 * @returns Boolean 11757 * @short Returns true if the number is odd. 11758 * 11759 * @example 11760 * 11761 * (3).isOdd() -> true 11762 * (18).isOdd() -> false 11763 * 11764 ***/ 11765 'isOdd': function(n) { 11766 return isInteger(n) && !isMultipleOf(n, 2); 11767 }, 11768 11769 /*** 11770 * @method isEven() 11771 * @returns Boolean 11772 * @short Returns true if the number is even. 11773 * 11774 * @example 11775 * 11776 * (6).isEven() -> true 11777 * (17).isEven() -> false 11778 * 11779 ***/ 11780 'isEven': function(n) { 11781 return isMultipleOf(n, 2); 11782 }, 11783 11784 /*** 11785 * @method isMultipleOf(num) 11786 * @returns Boolean 11787 * @short Returns true if the number is a multiple of `num`. 11788 * 11789 * @example 11790 * 11791 * (6).isMultipleOf(2) -> true 11792 * (17).isMultipleOf(2) -> false 11793 * (32).isMultipleOf(4) -> true 11794 * (34).isMultipleOf(4) -> false 11795 * 11796 * @param {number} num 11797 * 11798 ***/ 11799 'isMultipleOf': function(n, num) { 11800 return isMultipleOf(n, num); 11801 }, 11802 11803 /*** 11804 * @method log([base] = Math.E) 11805 * @returns Number 11806 * @short Returns the logarithm of the number with `base`, or the natural 11807 * logarithm of the number if `base` is undefined. 11808 * 11809 * @example 11810 * 11811 * (64).log(2) -> 6 11812 * (9).log(3) -> 2 11813 * (5).log() -> 1.6094379124341003 11814 * 11815 * @param {number} [base] 11816 * 11817 ***/ 11818 'log': function(n, base) { 11819 return Math.log(n) / (base ? Math.log(base) : 1); 11820 }, 11821 11822 /*** 11823 * @method abbr([precision] = 0) 11824 * @returns String 11825 * @short Returns an abbreviated form of the number ("k" for thousand, "m" 11826 * for million, etc). 11827 * @extra [precision] will round to the given precision. `thousands` and 11828 * `decimal` allow custom separators to be used. 11829 * 11830 * @example 11831 * 11832 * (1000).abbr() -> "1k" 11833 * (1000000).abbr() -> "1m" 11834 * (1280).abbr(1) -> "1.3k" 11835 * 11836 * @param {number} [precision] 11837 * 11838 ***/ 11839 'abbr': function(n, precision) { 11840 return abbreviateNumber(n, precision, BASIC_UNITS); 11841 }, 11842 11843 /*** 11844 * @method metric([precision] = 0, [units] = "nμm|k") 11845 * @returns String 11846 * @short Returns the number as a string in metric notation. 11847 * @extra [precision] will round to the given precision (can be negative). 11848 * [units] is a string that determines both the unit notation and the 11849 * min/max unit allowed. The default is natural notation for common 11850 * units (meters, grams, etc). "all" can be passed for [units] and is a 11851 * shortcut to all standard SI units. The token `,` if present separates 11852 * units, otherwise each character is a unit. The token `|` if present 11853 * marks where fractional units end, otherwise no fractional units are 11854 * used. Finally, the token `_` if present is a placeholder for no unit. 11855 * 11856 * @example 11857 * 11858 * (1000).metric() -> "1k" 11859 * (1000000).metric() -> "1,000k" 11860 * (1249).metric(2) + 'g' -> "1.25kg" 11861 * (0.025).metric() + 'm' -> "25mm" 11862 * (1000000).metric(0, 'nμm|kM') -> "1M" 11863 * 11864 * @param {number} [precision] 11865 * @param {string} [units] 11866 * 11867 ***/ 11868 'metric': function(n, precision, units) { 11869 if (units === 'all') { 11870 units = METRIC_UNITS_FULL; 11871 } else if (!units) { 11872 units = METRIC_UNITS_SHORT; 11873 } 11874 return abbreviateNumber(n, precision, units); 11875 }, 11876 11877 /*** 11878 * @method bytes([precision] = 0, [binary] = false, [units] = 'si') 11879 * @returns String 11880 * @short Returns an abbreviated form of the number, with 'B' on the end for "bytes".
11881 * @extra [precision] will round to the given precision. If [binary] is `true`, 11882 * powers of 1024 will be used instead of 1000, and units will default 11883 * to the binary units "KiB", "MiB", etc. Units can be overridden by 11884 * passing "si" or "binary" for [units], or further customized by 11885 * passing a unit string. See `metric` for more. 11886 * 11887 * @example 11888 * 11889 * (1000).bytes() -> "1KB" 11890 * (1289).bytes(2) -> "1.29KB" 11891 * (1000).bytes(2, true) -> "0.98KiB" 11892 * (1000).bytes(2, true, 'si') -> "0.98KB" 11893 * 11894 * @param {number} [precision] 11895 * @param {boolean} [binary] 11896 * @param {string} [units] 11897 * 11898 ***/ 11899 'bytes': function(n, precision, binary, units) { 11900 if (units === 'binary' || (!units && binary)) { 11901 units = MEMORY_BINARY_UNITS; 11902 } else if (units === 'si' || !units) { 11903 units = MEMORY_UNITS; 11904 } 11905 return abbreviateNumber(n, precision, units, binary) + 'B'; 11906 }, 11907 11908 /*** 11909 * @method format([place] = 0) 11910 * @returns String 11911 * @short Formats the number to a readable string. 11912 * @extra If [place] is `undefined`, the place will automatically be determined. 11913 * `thousands` and `decimal` allow custom markers to be used. 11914 * 11915 * @example 11916 * 11917 * (56782).format() -> '56,782' 11918 * (56782).format(2) -> '56,782.00' 11919 * (4388.43).format(2) -> '4,388.43' 11920 * 11921 * @param {number} [place] 11922 * 11923 ***/ 11924 'format': function(n, place) { 11925 return numberFormat(n, place); 11926 }, 11927 11928 /*** 11929 * @method hex([pad] = 1) 11930 * @returns String 11931 * @short Converts the number to hexidecimal. 11932 * @extra [pad] will pad the resulting string to that many places. 11933 * 11934 * @example 11935 * 11936 * (255).hex() -> 'ff'; 11937 * (255).hex(4) -> '00ff'; 11938 * (23654).hex() -> '5c66'; 11939 * 11940 * @param {number} [pad] 11941 * 11942 ***/ 11943 'hex': function(n, pad) { 11944 return padNumber(n, pad || 1, false, 16); 11945 }, 11946 11947 /*** 11948 * @method times(fn) 11949 * @returns Mixed 11950 * @short Calls `fn` a number of times equivalent to the number. 11951 * @extra Any non-undefined return values of `fn` will be collected and 11952 * returned in an array. 11953 * 11954 * @callback indexMapFn 11955 * 11956 * i The index of the current iteration. 11957 * 11958 * @example 11959 * 11960 * (8).times(logHello) -> logs "hello" 8 times 11961 * (7).times(function(n) { 11962 * return Math.pow(2, n); 11963 * }); 11964 * 11965 * @callbackParam {number} i 11966 * @callbackReturns {any} indexMapFn 11967 * @param {indexMapFn} fn 11968 * 11969 ***/ 11970 'times': function(n, fn) { 11971 var arr, result; 11972 for (var i = 0; i < n; i++) { 11973 result = fn.call(n, i); 11974 if (isDefined(result)) { 11975 if (!arr) { 11976 arr = []; 11977 } 11978 arr.push(result); 11979 } 11980 } 11981 return arr; 11982 }, 11983 11984 /*** 11985 * @method chr() 11986 * @returns String 11987 * @short Returns a string at the code point of the number. 11988 * 11989 * @example 11990 * 11991 * (65).chr() -> "A" 11992 * (75).chr() -> "K" 11993 * 11994 ***/ 11995 'chr': function(n) { 11996 return chr(n); 11997 }, 11998 11999 /*** 12000 * @method pad([place] = 0, [sign] = false, [base] = 10) 12001 * @returns String 12002 * @short Pads a number with "0" to `place`. 12003 * @extra [sign] allows you to force the sign as well (+05, etc). [base] can 12004 * change the base for numeral conversion. 12005 * 12006 * @example 12007 * 12008 * (5).pad(2) -> '05' 12009 * (-5).pad(4) -> '-0005' 12010 * (82).pad(3, true) -> '+082' 12011 * 12012 * @param {number} place 12013 * @param {boolean} [sign] 12014 * @param {number} [base] 12015 * 12016 ***/ 12017 'pad': function(n, place, sign, base) { 12018 return padNumber(n, place, sign, base); 12019 }, 12020 12021 /*** 12022 * @method ordinalize() 12023 * @returns String 12024 * @short Returns an ordinalized English string, i.e. "1st", "2nd", etc. 12025 * 12026 * @example 12027 * 12028 * (1).ordinalize() -> '1st'; 12029 * (2).ordinalize() -> '2nd'; 12030 * (8).ordinalize() -> '8th'; 12031 * 12032 ***/ 12033 'ordinalize': function(n) { 12034 var num = abs(n), last = +num.toString().slice(-2); 12035 return n + getOrdinalSuffix(last); 12036 }, 12037 12038 /*** 12039 * @method toNumber() 12040 * @returns Number 12041 * @short Identity function for compatibilty. 12042 * 12043 * @example 12044 * 12045 * (420).toNumber() -> 420 12046 * 12047 ***/ 12048 'toNumber': function(n) { 12049 return n.valueOf(); 12050 }, 12051 12052 /*** 12053 * @method round([precision] = 0) 12054 * @returns Number 12055 * @short Shortcut for `Math.round` that also allows a `precision`. 12056 * 12057 * @example 12058 * 12059 * (3.241).round() -> 3 12060 * (-3.841).round() -> -4 12061 * (3.241).round(2) -> 3.24 12062 * (3748).round(-2) -> 3800 12063 * 12064 * @param {number} [precision] 12065 * 12066 ***/ 12067 'round': createRoundingFunction(round), 12068 12069 /*** 12070 * @method ceil([precision] = 0) 12071 * @returns Number 12072 * @short Shortcut for `Math.ceil` that also allows a `precision`. 12073 * 12074 * @example 12075 * 12076 * (3.241).ceil() -> 4 12077 * (-3.241).ceil() -> -3 12078 * (3.241).ceil(2) -> 3.25 12079 * (3748).ceil(-2) -> 3800 12080 * 12081 * @param {number} [precision] 12082 * 12083 ***/ 12084 'ceil': createRoundingFunction(ceil), 12085 12086 /*** 12087 * @method floor([precision] = 0) 12088 * @returns Number 12089 * @short Shortcut for `Math.floor` that also allows a `precision`. 12090 * 12091 * @example 12092 * 12093 * (3.241).floor() -> 3 12094 * (-3.841).floor() -> -4 12095 * (3.241).floor(2) -> 3.24 12096 * (3748).floor(-2) -> 3700 12097 * 12098 * @param {number} [precision] 12099 * 12100 ***/ 12101 'floor': createRoundingFunction(floor) 12102 12103 });
12104 12105 /*** 12106 * @method [math]() 12107 * @returns Number 12108 * @short Math related functions are mapped as shortcuts to numbers and are 12109 * identical. Note that `log` provides some special defaults. 12110 * 12111 * @set 12112 * abs 12113 * sin 12114 * asin 12115 * cos 12116 * acos 12117 * tan 12118 * atan 12119 * sqrt 12120 * exp 12121 * pow 12122 * 12123 * @example 12124 * 12125 * (3).pow(3) -> 27 12126 * (-3).abs() -> 3 12127 * (1024).sqrt() -> 32 12128 * 12129 ***/ 12130 function buildMathAliases() { 12131 defineInstanceSimilar(sugarNumber, 'abs pow sin asin cos acos tan atan exp pow sqrt', function(methods, name) { 12132 methods[name] = function(n, arg) { 12133 // Note that .valueOf() here is only required due to a 12134 // very strange bug in iOS7 that only occurs occasionally 12135 // in which Math.abs() called on non-primitive numbers 12136 // returns a completely different number (Issue #400) 12137 return Math[name](n.valueOf(), arg); 12138 }; 12139 }); 12140 } 12141 12142 buildMathAliases(); 12143 12144 /*** 12145 * @module Function 12146 * @description Lazy, throttled, and memoized functions, delayed functions and 12147 * handling of timers, argument currying. 12148 * 12149 ***/ 12150 12151 var _lock = privatePropertyAccessor('lock'); 12152 var _timers = privatePropertyAccessor('timers'); 12153 var _partial = privatePropertyAccessor('partial'); 12154 var _canceled = privatePropertyAccessor('canceled'); 12155 12156 var createInstanceFromPrototype = Object.create || function(prototype) { 12157 var ctor = function() { }; 12158 ctor.prototype = prototype; 12159 return new ctor; 12160 }; 12161 12162 function setDelay(fn, ms, after, scope, args) { 12163 // Delay of infinity is never called of course... 12164 ms = coercePositiveInteger(ms || 0); 12165 if (!_timers(fn)) { 12166 _timers(fn, []); 12167 } 12168 // This is a workaround for <= IE8, which apparently has the 12169 // ability to call timeouts in the queue on the same tick (ms?) 12170 // even if functionally they have already been cleared. 12171 _canceled(fn, false); 12172 _timers(fn).push(setTimeout(function() { 12173 if (!_canceled(fn)) { 12174 after.apply(scope, args || []); 12175 } 12176 }, ms)); 12177 } 12178 12179 function cancelFunction(fn) { 12180 var timers = _timers(fn), timer; 12181 if (isArray(timers)) { 12182 while (timer = timers.shift()) { 12183 clearTimeout(timer); 12184 } 12185 } 12186 _canceled(fn, true); 12187 return fn; 12188 } 12189 12190 function createLazyFunction(fn, ms, immediate, limit) { 12191 var queue = [], locked = false, execute, rounded, perExecution, result; 12192 ms = ms || 1; 12193 limit = limit || Infinity; 12194 rounded = ceil(ms); 12195 perExecution = round(rounded / ms) || 1; 12196 execute = function() { 12197 var queueLength = queue.length, maxPerRound; 12198 if (queueLength == 0) return; 12199 // Allow fractions of a millisecond by calling 12200 // multiple times per actual timeout execution 12201 maxPerRound = max(queueLength - perExecution, 0); 12202 while (queueLength > maxPerRound) { 12203 // Getting uber-meta here... 12204 result = Function.prototype.apply.apply(fn, queue.shift()); 12205 queueLength--; 12206 } 12207 setDelay(lazy, rounded, function() { 12208 locked = false; 12209 execute(); 12210 }); 12211 }; 12212 function lazy() { 12213 // If the execution has locked and it's immediate, then 12214 // allow 1 less in the queue as 1 call has already taken place. 12215 if (queue.length < limit - (locked && immediate ? 1 : 0)) { 12216 // Optimized: no leaking arguments 12217 var args = []; for (var $i = 0, $len = arguments.length; $i < $len; $i++) args.push(arguments[$i]); 12218 queue.push([this, args]); 12219 } 12220 if (!locked) { 12221 locked = true; 12222 if (immediate) { 12223 execute(); 12224 } else { 12225 setDelay(lazy, rounded, execute); 12226 } 12227 } 12228 // Return the memoized result 12229 return result; 12230 } 12231 return lazy; 12232 } 12233 12234 // Collecting arguments in an array instead of 12235 // passing back the arguments object which will 12236 // deopt this function in V8. 12237 function collectArguments() { 12238 var args = arguments, i = args.length, arr = new Array(i); 12239 while (i--) { 12240 arr[i] = args[i];
12241 } 12242 return arr; 12243 } 12244 12245 function createHashedMemoizeFunction(fn, hashFn, limit) { 12246 var map = {}, refs = [], counter = 0; 12247 return function() { 12248 var hashObj = hashFn.apply(this, arguments); 12249 var key = serializeInternal(hashObj, refs); 12250 if (hasOwn(map, key)) { 12251 return getOwn(map, key); 12252 } 12253 if (counter === limit) { 12254 map = {}; 12255 refs = []; 12256 counter = 0; 12257 } 12258 counter++; 12259 return map[key] = fn.apply(this, arguments); 12260 }; 12261 } 12262 12263 defineInstance(sugarFunction, { 12264 12265 /*** 12266 * @method lazy([ms] = 1, [immediate] = false, [limit] = Infinity) 12267 * @returns Function 12268 * @short Creates a lazy function that, when called repeatedly, will queue 12269 * execution and wait [ms] milliseconds to execute. 12270 * @extra If [immediate] is `true`, first execution will happen immediately, 12271 * then lock. If [limit] is a fininte number, calls past [limit] will 12272 * be ignored while execution is locked. Compare this to `throttle`, 12273 * which will execute only once per [ms] milliseconds. Note that [ms] 12274 * can also be a fraction. Calling `cancel` on a lazy function will 12275 * clear the entire queue. 12276 * 12277 * @example 12278 * 12279 * var fn = logHello.lazy(250); 12280 * runTenTimes(fn); -> Logs 10 times each time 250ms later 12281 * 12282 * var fn = logHello.lazy(250, false, 5); 12283 * runTenTimes(fn); -> Logs 5 times each time 250ms later 12284 * 12285 * @param {number} [ms] 12286 * @param {number} [limit] 12287 * @param {boolean} [immediate] 12288 * 12289 ***/ 12290 'lazy': function(fn, ms, immediate, limit) { 12291 return createLazyFunction(fn, ms, immediate, limit); 12292 }, 12293 12294 /*** 12295 * @method throttle([ms] = 1) 12296 * @returns Function 12297 * @short Creates a "throttled" version of the function that will only be 12298 * executed once per `ms` milliseconds. 12299 * @extra This is functionally equivalent to calling `lazy` with a [limit] of 12300 * `1` and [immediate] as `true`. `throttle` is appropriate when you 12301 * want to make sure a function is only executed at most once for a 12302 * given duration. 12303 * 12304 * @example 12305 * 12306 * var fn = logHello.throttle(50); 12307 * runTenTimes(fn); 12308 * 12309 * @param {number} [ms] 12310 * 12311 ***/ 12312 'throttle': function(fn, ms) { 12313 return createLazyFunction(fn, ms, true, 1); 12314 }, 12315 12316 /*** 12317 * @method debounce([ms] = 1) 12318 * @returns Function 12319 * @short Creates a "debounced" function that postpones its execution until 12320 * after `ms` milliseconds have passed. 12321 * @extra This method is useful to execute a function after things have 12322 * "settled down". A good example of this is when a user tabs quickly 12323 * through form fields, execution of a heavy operation should happen 12324 * after a few milliseconds when they have "settled" on a field. 12325 * 12326 * @example 12327 * 12328 * var fn = logHello.debounce(250) 12329 * runTenTimes(fn); -> called once 250ms later 12330 * 12331 * @param {number} [ms] 12332 * 12333 ***/ 12334 'debounce': function(fn, ms) { 12335 function debounced() { 12336 // Optimized: no leaking arguments 12337 var args = []; for (var $i = 0, $len = arguments.length; $i < $len; $i++) args.push(arguments[$i]); 12338 cancelFunction(debounced); 12339 setDelay(debounced, ms, fn, this, args); 12340 } 12341 return debounced; 12342 }, 12343 12344 /*** 12345 * @method cancel() 12346 * @returns Function 12347 * @short Cancels a delayed function scheduled to be run. 12348 * @extra `delay`, `lazy`, `throttle`, and `debounce` can all set delays. 12349 * 12350 * @example 12351 * 12352 * logHello.delay(500).cancel() -> never logs 12353 * 12354 ***/ 12355 'cancel': function(fn) { 12356 return cancelFunction(fn); 12357 }, 12358 12359 /*** 12360 * @method after(n) 12361 * @returns Function 12362 * @short Creates a function that will execute after `n` calls. 12363 * @extra `after` is useful for running a final callback after a specific 12364 * number of operations, often when the order in which the operations 12365 * will complete is unknown. The created function will be passed an 12366 * array of the arguments that it has collected from each after `n`. 12367 * Note that the function will execute on every call after `n`. 12368 * Use `once` in conjunction with this method to prevent being 12369 * triggered by subsequent calls. 12370 * 12371 * @example 12372 * 12373 * var fn = logHello.after(5); 12374 * runTenTimes(fn); -> logs 6 times 12375 * 12376 * var fn = logHello.once().after(5) 12377 * runTenTimes(fn); -> logs once 12378 * 12379 * @param {number} [n] 12380 * 12381 ***/ 12382 'after': function(fn, num) { 12383 var count = 0, collectedArgs = []; 12384 num = coercePositiveInteger(num); 12385 return function() { 12386 // Optimized: no leaking arguments 12387 var args = []; for (var $i = 0, $len = arguments.length; $i < $len; $i++) args.push(arguments[$i]); 12388 collectedArgs.push(args); 12389 count++; 12390 if (count >= num) {
12391 return fn.call(this, collectedArgs); 12392 } 12393 }; 12394 }, 12395 12396 /*** 12397 * @method once() 12398 * @returns Function 12399 * @short Creates a function that will execute only once and store the result. 12400 * @extra `once` is useful for creating functions that will cache the result 12401 * of an expensive operation and use it on subsequent calls. Also it 12402 * can be useful for creating initialization functions that only need 12403 * to be run once. 12404 * 12405 * @example 12406 * 12407 * var fn = logHello.once(); 12408 * runTenTimes(fn); -> logs once 12409 * 12410 ***/ 12411 'once': function(fn) { 12412 var called = false, val; 12413 return function() { 12414 if (called) { 12415 return val; 12416 } 12417 called = true; 12418 return val = fn.apply(this, arguments); 12419 }; 12420 }, 12421 12422 /*** 12423 * @method memoize([hashFn], [limit]) 12424 * @returns Function 12425 * @short Creates a function that will memoize results for unique calls. 12426 * @extra `memoize` can be thought of as a more powerful `once`. Where `once` 12427 * will only call a function once ever, memoized functions will be 12428 * called once per unique call. A "unique call" is determined by the 12429 * return value of [hashFn], which is passed the arguments of each call. 12430 * If [hashFn] is undefined, it will deeply serialize all arguments, 12431 * such that any different argument signature will result in a unique 12432 * call. [hashFn] may be a string (allows `deep properties`) that acts 12433 * as a shortcut to return a property of the first argument passed. 12434 * [limit] sets an upper limit on memoized results. The default is no 12435 * limit, meaning that unique calls will continue to memoize results. 12436 * For most use cases this is fine, however [limit] is useful for more 12437 * persistent (often server-side) applications for whom memory leaks 12438 * are a concern. 12439 * 12440 * @example 12441 * 12442 * var fn = logHello.memoize(); 12443 * fn(1); fn(1); fn(2); -> logs twice, memoizing once 12444 * 12445 * var fn = calculateUserBalance.memoize('id'); 12446 * fn(Harry); fn(Mark); fn(Mark); -> logs twice, memoizing once 12447 * 12448 * @param {string|Function} [hashFn] 12449 * @param {number} [limit] 12450 * 12451 ***/ 12452 'memoize': function(fn, arg1, arg2) { 12453 var hashFn, limit, prop; 12454 if (isNumber(arg1)) { 12455 limit = arg1; 12456 } else { 12457 hashFn = arg1; 12458 limit = arg2; 12459 } 12460 if (isString(hashFn)) { 12461 prop = hashFn; 12462 hashFn = function(obj) { 12463 return deepGetProperty(obj, prop); 12464 }; 12465 } else if (!hashFn) { 12466 hashFn = collectArguments; 12467 } 12468 return createHashedMemoizeFunction(fn, hashFn, limit); 12469 }, 12470 12471 /*** 12472 * @method lock([n]) 12473 * @returns Function 12474 * @short Locks the number of arguments accepted by the function. 12475 * @extra If not passed, [n] will be the length of the function. This method 12476 * can be called on functions created by `partial`, in which case it 12477 * will lock the total arguments during execution. 12478 * 12479 * @example 12480 * 12481 * logArgs.lock(2)(1,2,3) -> logs 1,2 12482 * 12483 * @param {number} [n] 12484 * 12485 ***/ 12486 'lock': function(fn, n) { 12487 var lockedFn; 12488 if (_partial(fn)) { 12489 _lock(fn, isNumber(n) ? n : null); 12490 return fn; 12491 } 12492 lockedFn = function() { 12493 arguments.length = min(_lock(lockedFn), arguments.length); 12494 return fn.apply(this, arguments); 12495 }; 12496 _lock(lockedFn, isNumber(n) ? n : fn.length); 12497 return lockedFn; 12498 } 12499 12500 });
12501 12502 defineInstanceWithArguments(sugarFunction, { 12503 12504 /*** 12505 * @method partial([arg1], [arg2], ...) 12506 * @returns Function 12507 * @short Returns a new version of the function which has part of its arguments 12508 * pre-emptively filled in, also known as "currying". 12509 * @extra `undefined` can be passed as any argument, and is a placeholder that 12510 * will be replaced with arguments passed when the function is executed. 12511 * This allows currying of arguments even when they occur toward the end 12512 * of an argument list (the example demonstrates this more clearly). 12513 * 12514 * @example 12515 * 12516 * logArgs.partial(undefined, 'b')('a') -> logs a, b 12517 * 12518 * @param {any} [arg1] 12519 * @param {any} [arg2] 12520 * 12521 ***/ 12522 'partial': function(fn, curriedArgs) { 12523 var curriedLen = curriedArgs.length; 12524 var partialFn = function() { 12525 var argIndex = 0, applyArgs = [], self = this, lock = _lock(partialFn), result, i; 12526 for (i = 0; i < curriedLen; i++) { 12527 var arg = curriedArgs[i]; 12528 if (isDefined(arg)) { 12529 applyArgs[i] = arg; 12530 } else { 12531 applyArgs[i] = arguments[argIndex++]; 12532 } 12533 } 12534 for (i = argIndex; i < arguments.length; i++) { 12535 applyArgs.push(arguments[i]); 12536 } 12537 if (lock === null) { 12538 lock = curriedLen; 12539 } 12540 if (isNumber(lock)) { 12541 applyArgs.length = min(applyArgs.length, lock); 12542 } 12543 // If the bound "this" object is an instance of the partialed 12544 // function, then "new" was used, so preserve the prototype 12545 // so that constructor functions can also be partialed. 12546 if (self instanceof partialFn) { 12547 self = createInstanceFromPrototype(fn.prototype); 12548 result = fn.apply(self, applyArgs); 12549 // An explicit return value is allowed from constructors 12550 // as long as they are of "object" type, so return the 12551 // correct result here accordingly. 12552 return isObjectType(result) ? result : self; 12553 } 12554 return fn.apply(self, applyArgs); 12555 }; 12556 _partial(partialFn, true); 12557 return partialFn; 12558 }, 12559 12560 /*** 12561 * @method delay([ms] = 1, [arg1], [arg2], ...) 12562 * @returns Function 12563 * @short Executes the function after `ms` milliseconds. 12564 * @extra Returns a reference to itself. `delay` is also a way to execute non- 12565 * blocking operations that will wait until the CPU is free. Delayed 12566 * functions can be canceled using the `cancel` method. Can also curry 12567 * arguments passed in after `ms`. 12568 * 12569 * @example 12570 * 12571 * logHello.delay(500) -> logs after 500ms 12572 * logArgs.delay(500, 'a') -> logs "a" after 500ms 12573 * 12574 * @param {number} [ms] 12575 * @param {any} [arg1] 12576 * @param {any} [arg2] 12577 * 12578 ***/ 12579 'delay': function(fn, ms, args) { 12580 setDelay(fn, ms, fn, fn, args); 12581 return fn; 12582 }, 12583 12584 /*** 12585 * @method every([ms] = 1, [arg1], [arg2], ...) 12586 * @returns Function 12587 * @short Executes the function every `ms` milliseconds. 12588 * @extra Returns a reference to itself. `every` uses `setTimeout`, which 12589 * means that you are guaranteed a period of idle time equal to [ms] 12590 * after execution has finished. Compare this to `setInterval` which 12591 * will try to run a function every [ms], even when execution itself 12592 * takes up a portion of that time. In most cases avoiding `setInterval` 12593 * is better as calls won't "back up" when the CPU is under strain, 12594 * however this also means that calls are less likely to happen at 12595 * exact intervals of [ms], so the use case here should be considered. 12596 * Additionally, `every` can curry arguments passed in after [ms], and 12597 * also be canceled with `cancel`. 12598 * 12599 * @example 12600 * 12601 * logHello.every(1000) -> logs every second 12602 * logArgs.every(1000, 'Hola') -> logs 'hola' every second 12603 * 12604 * @param {number} [ms] 12605 * @param {any} [arg1] 12606 * @param {any} [arg2] 12607 * 12608 ***/ 12609 'every': function(fn, ms, args) { 12610 function execute() { 12611 // Set the delay first here, so that cancel 12612 // can be called within the executing function. 12613 setDelay(fn, ms, execute); 12614 fn.apply(fn, args); 12615 } 12616 setDelay(fn, ms, execute); 12617 return fn; 12618 } 12619 12620 });
12621 12622 /*** 12623 * @module RegExp 12624 * @description RegExp escaping and flag manipulation. 12625 * 12626 * Note here that methods on the RegExp class like .exec and .test will fail in 12627 * the current version of SpiderMonkey being used by CouchDB when using 12628 * shorthand regex notation like /foo/. This is the reason for the intermixed 12629 * use of shorthand and compiled regexes here. If you're using JS in CouchDB, it 12630 * is safer to ALWAYS compile your regexes from a string. 12631 * 12632 ***/ 12633 12634 defineStatic(sugarRegExp, { 12635 12636 /*** 12637 * @method escape([str] = '') 12638 * @returns String 12639 * @static 12640 * @short Escapes all RegExp tokens in a string. 12641 * 12642 * @example 12643 * 12644 * RegExp.escape('really?') -> 'really\?' 12645 * RegExp.escape('yes.') -> 'yes\.' 12646 * RegExp.escape('(not really)') -> '\(not really\)' 12647 * 12648 * @param {string} str 12649 * 12650 ***/ 12651 'escape': function(str) { 12652 return escapeRegExp(str); 12653 } 12654 12655 }); 12656 12657 defineInstance(sugarRegExp, { 12658 12659 /*** 12660 * @method getFlags() 12661 * @returns String 12662 * @short Returns the flags of the regex as a string. 12663 * 12664 * @example 12665 * 12666 * /texty/gim.getFlags() -> 'gim' 12667 * 12668 ***/ 12669 'getFlags': function(r) { 12670 return getRegExpFlags(r); 12671 }, 12672 12673 /*** 12674 * @method setFlags(flags) 12675 * @returns RegExp 12676 * @short Creates a copy of the regex with `flags` set. 12677 * 12678 * @example 12679 * 12680 * /texty/.setFlags('gim') -> now has global, ignoreCase, and multiline set 12681 * 12682 * @param {string} flags 12683 * 12684 ***/ 12685 'setFlags': function(r, flags) { 12686 return RegExp(r.source, flags); 12687 }, 12688 12689 /*** 12690 * @method addFlags(flags) 12691 * @returns RegExp 12692 * @short Creates a copy of the regex with `flags` added. 12693 * 12694 * @example 12695 * 12696 * /texty/.addFlags('g') -> /texty/g 12697 * /texty/.addFlags('im') -> /texty/im 12698 * 12699 * @param {string} flags 12700 * 12701 ***/ 12702 'addFlags': function(r, flags) { 12703 return RegExp(r.source, getRegExpFlags(r, flags)); 12704 }, 12705 12706 /*** 12707 * @method removeFlags(flags) 12708 * @returns RegExp 12709 * @short Creates a copy of the regex with `flags` removed. 12710 * 12711 * @example 12712 * 12713 * /texty/gim.removeFlags('g') -> /texty/im 12714 * /texty/gim.removeFlags('im') -> /texty/g 12715 * 12716 * @param {string} flags 12717 * 12718 ***/ 12719 'removeFlags': function(r, flags) { 12720 var reg = allCharsReg(flags); 12721 return RegExp(r.source, getRegExpFlags(r).replace(reg, '')); 12722 } 12723 12724 }); 12725 12726 /*** 12727 * @module Range 12728 * @description Date, Number, and String ranges that can be manipulated and compared, 12729 * or enumerate over specific points within the range. 12730 * 12731 ***/ 12732 12733 var DURATION_UNITS = 'year|month|week|day|hour|minute|second|millisecond'; 12734 var DURATION_REG = RegExp('(\\d+)?\\s*(' + DURATION_UNITS + ')s?', 'i'); 12735 12736 var MULTIPLIERS = { 12737 'Hours': 60 * 60 * 1000, 12738 'Minutes': 60 * 1000, 12739 'Seconds': 1000, 12740 'Milliseconds': 1 12741 }; 12742 12743 var PrimitiveRangeConstructor = function(start, end) { 12744 return new Range(start, end); 12745 }; 12746 12747 function Range(start, end) { 12748 this.start = cloneRangeMember(start); 12749 this.end = cloneRangeMember(end); 12750 } 12751 12752 function getRangeMemberNumericValue(m) { 12753 return isString(m) ? m.charCodeAt(0) : m; 12754 } 12755 12756 function getRangeMemberPrimitiveValue(m) { 12757 if (m == null) return m; 12758 return isDate(m) ? m.getTime() : m.valueOf(); 12759 } 12760 12761 function getPrecision(n) { 12762 var split = periodSplit(n.toString()); 12763 return split[1] ? split[1].length : 0; 12764 } 12765 12766 function getGreaterPrecision(n1, n2) { 12767 return max(getPrecision(n1), getPrecision(n2)); 12768 } 12769 12770 function cloneRangeMember(m) { 12771 if (isDate(m)) { 12772 return new Date(m.getTime()); 12773 } else { 12774 return getRangeMemberPrimitiveValue(m); 12775 } 12776 } 12777 12778 function isValidRangeMember(m) { 12779 var val = getRangeMemberPrimitiveValue(m); 12780 return (!!val || val === 0) && valueIsNotInfinite(m); 12781 } 12782 12783 function valueIsNotInfinite(m) { 12784 return m !== -Infinity && m !== Infinity; 12785 } 12786 12787 function rangeIsValid(range) { 12788 return isValidRangeMember(range.start) && 12789 isValidRangeMember(range.end) && 12790 typeof range.start === typeof range.end; 12791 } 12792 12793 function rangeEvery(range, step, countOnly, fn) { 12794 var increment, 12795 precision, 12796 dio, 12797 unit, 12798 start = range.start, 12799 end = range.end, 12800 inverse = end < start, 12801 current = start, 12802 index = 0, 12803 result = []; 12804 12805 if (!rangeIsValid(range)) { 12806 return []; 12807 } 12808 if (isFunction(step)) { 12809 fn = step; 12810 step = null; 12811 } 12812 step = step || 1; 12813 if (isNumber(start)) { 12814 precision = getGreaterPrecision(start, step); 12815 increment = function() { 12816 return incrementNumber(current, step, precision); 12817 }; 12818 } else if (isString(start)) { 12819 increment = function() { 12820 return incrementString(current, step); 12821 }; 12822 } else if (isDate(start)) { 12823 dio = getDateIncrementObject(step); 12824 step = dio[0]; 12825 unit = dio[1]; 12826 increment = function() { 12827 return incrementDate(current, step, unit); 12828 }; 12829 } 12830 // Avoiding infinite loops 12831 if (inverse && step > 0) { 12832 step *= -1; 12833 } 12834 while (inverse ? current >= end : current <= end) { 12835 if (!countOnly) { 12836 result.push(current); 12837 } 12838 if (fn) { 12839 fn(current, index, range); 12840 } 12841 current = increment(); 12842 index++; 12843 } 12844 return countOnly ? index - 1 : result; 12845 } 12846 12847 function getDateIncrementObject(amt) { 12848 var match, val, unit; 12849 if (isNumber(amt)) { 12850 return [amt, 'Milliseconds']; 12851 } 12852 match = amt.match(DURATION_REG); 12853 val = +match[1] || 1; 12854 unit = simpleCapitalize(match[2].toLowerCase()); 12855 if (unit.match(/hour|minute|second/i)) { 12856 unit += 's'; 12857 } else if (unit === 'Year') { 12858 unit = 'FullYear'; 12859 } else if (unit === 'Week') { 12860 unit = 'Date'; 12861 val *= 7; 12862 } else if (unit === 'Day') { 12863 unit = 'Date'; 12864 } 12865 return [val, unit]; 12866 } 12867 12868 function incrementDate(src, amount, unit) { 12869 var mult = MULTIPLIERS[unit], d; 12870 if (mult) { 12871 d = new Date(src.getTime() + (amount * mult)); 12872 } else { 12873 d = new Date(src); 12874 callDateSet(d, unit, callDateGet(src, unit) + amount); 12875 } 12876 return d; 12877 } 12878 12879 function incrementString(current, amount) { 12880 return chr(current.charCodeAt(0) + amount); 12881 } 12882 12883 function incrementNumber(current, amount, precision) { 12884 return withPrecision(current + amount, precision); 12885 } 12886 12887 function rangeClamp(range, obj) { 12888 var clamped, 12889 start = range.start, 12890 end = range.end, 12891 min = end < start ? end : start, 12892 max = start > end ? start : end; 12893 if (obj < min) { 12894 clamped = min; 12895 } else if (obj > max) { 12896 clamped = max; 12897 } else { 12898 clamped = obj;
12899 } 12900 return cloneRangeMember(clamped); 12901 } 12902 12903 defineOnPrototype(Range, { 12904 12905 /*** 12906 * @method toString() 12907 * @returns String 12908 * @short Returns a string representation of the range. 12909 * 12910 * @example 12911 * 12912 * Number.range(1, 5).toString() -> 1..5 12913 * janToMay.toString() -> January 1, xxxx..May 1, xxxx 12914 * 12915 ***/ 12916 'toString': function() { 12917 return rangeIsValid(this) ? this.start + '..' + this.end : 'Invalid Range'; 12918 }, 12919 12920 /*** 12921 * @method isValid() 12922 * @returns Boolean 12923 * @short Returns true if the range is valid, false otherwise. 12924 * 12925 * @example 12926 * 12927 * janToMay.isValid() -> true 12928 * Number.range(NaN, NaN).isValid() -> false 12929 * 12930 ***/ 12931 'isValid': function() { 12932 return rangeIsValid(this); 12933 }, 12934 12935 /*** 12936 * @method span() 12937 * @returns Number 12938 * @short Returns the span of the range. If the range is a date range, the 12939 * value is in milliseconds. 12940 * @extra The span includes both the start and the end. 12941 * 12942 * @example 12943 * 12944 * Number.range(5, 10).span() -> 6 12945 * Number.range(40, 25).span() -> 16 12946 * janToMay.span() -> 10368000001 (or more depending on leap year) 12947 * 12948 ***/ 12949 'span': function() { 12950 var n = getRangeMemberNumericValue(this.end) - getRangeMemberNumericValue(this.start); 12951 return rangeIsValid(this) ? abs(n) + 1 : NaN; 12952 }, 12953 12954 /*** 12955 * @method contains(el) 12956 * @returns Boolean 12957 * @short Returns true if `el` is contained inside the range. `el` may be a 12958 * value or another range. 12959 * 12960 * @example 12961 * 12962 * Number.range(5, 10).contains(7) -> true 12963 * Number.range(5, 10).contains(2) -> false 12964 * janToMay.contains(mar) -> true 12965 * janToMay.contains(marToAug) -> false 12966 * janToMay.contains(febToApr) -> true 12967 * 12968 * @param {RangeElement} el 12969 * 12970 ***/ 12971 'contains': function(el) { 12972 if (el == null) return false; 12973 if (el.start && el.end) { 12974 return el.start >= this.start && el.start <= this.end && 12975 el.end >= this.start && el.end <= this.end; 12976 } else { 12977 return el >= this.start && el <= this.end; 12978 } 12979 }, 12980 12981 /*** 12982 * @method every(amount, [fn]) 12983 * @returns Array 12984 * @short Iterates through the range by `amount`, calling [fn] for each step. 12985 * @extra Returns an array of each increment visited. For date ranges, 12986 * `amount` can also be a string like `"2 days"`. This will step 12987 * through the range by incrementing a date object by that specific 12988 * unit, and so is generally preferable for vague units such as 12989 * `"2 months"`. 12990 * 12991 * @callback rangeEveryFn 12992 * 12993 * el The element of the current iteration. 12994 * i The index of the current iteration. 12995 * r A reference to the range. 12996 * 12997 * @example 12998 * 12999 * Number.range(2, 8).every(2) -> [2,4,6,8] 13000 * janToMay.every('2 months') -> [Jan 1, Mar 1, May 1] 13001 * 13002 * sepToOct.every('week', function() { 13003 * // Will be called every week from September to October 13004 * }) 13005 * 13006 * @param {string|number} amount 13007 * @param {rangeEveryFn} [fn] 13008 * @callbackParam {RangeElement} el 13009 * @callbackParam {number} i 13010 * @callbackParam {Range} r 13011 * 13012 ***/ 13013 'every': function(amount, fn) { 13014 return rangeEvery(this, amount, false, fn); 13015 }, 13016 13017 /*** 13018 * @method toArray() 13019 * @returns Array 13020 * @short Creates an array from the range. 13021 * @extra If the range is a date range, every millisecond between the start 13022 * and end dates will be returned. To control this use `every` instead. 13023 * 13024 * @example 13025 * 13026 * Number.range(1, 5).toArray() -> [1,2,3,4,5] 13027 * Date.range('1 millisecond ago', 'now').toArray() -> [1ms ago, now] 13028 * 13029 ***/ 13030 'toArray': function() { 13031 return rangeEvery(this); 13032 }, 13033 13034 /*** 13035 * @method union(range) 13036 * @returns Range 13037 * @short Returns a new range with the earliest starting point as its start, 13038 * and the latest ending point as its end. If the two ranges do not 13039 * intersect this will effectively remove the "gap" between them. 13040 * 13041 * @example 13042 * 13043 * oneToTen.union(fiveToTwenty) -> 1..20 13044 * janToMay.union(marToAug) -> Jan 1, xxxx..Aug 1, xxxx 13045 * 13046 * @param {Range} range 13047 * 13048 ***/ 13049 'union': function(range) { 13050 return new Range( 13051 this.start < range.start ? this.start : range.start, 13052 this.end > range.end ? this.end : range.end 13053 ); 13054 }, 13055 13056 /*** 13057 * @method intersect(range) 13058 * @returns Range 13059 * @short Returns a new range with the latest starting point as its start, 13060 * and the earliest ending point as its end. If the two ranges do not 13061 * intersect this will effectively produce an invali
13061d range. 13062 * 13063 * @example 13064 * 13065 * oneToTen.intersect(fiveToTwenty) -> 5..10 13066 * janToMay.intersect(marToAug) -> Mar 1, xxxx..May 1, xxxx 13067 * 13068 * @param {Range} range 13069 * 13070 ***/ 13071 'intersect': function(range) { 13072 if (range.start > this.end || range.end < this.start) { 13073 return new Range(NaN, NaN); 13074 } 13075 return new Range( 13076 this.start > range.start ? this.start : range.start, 13077 this.end < range.end ? this.end : range.end 13078 ); 13079 }, 13080 13081 /*** 13082 * @method clone() 13083 * @returns Range 13084 * @short Clones the range. 13085 * @extra Members of the range will also be cloned. 13086 * 13087 * @example 13088 * 13089 * Number.range(1, 5).clone() -> Returns a copy of the range. 13090 * 13091 ***/ 13092 'clone': function() { 13093 return new Range(this.start, this.end); 13094 }, 13095 13096 /*** 13097 * @method clamp(el) 13098 * @returns Mixed 13099 * @short Clamps `el` to be within the range if it falls outside. 13100 * 13101 * @example 13102 * 13103 * Number.range(1, 5).clamp(8) -> 5 13104 * janToMay.clamp(aug) -> May 1, xxxx 13105 * 13106 * @param {RangeElement} el 13107 * 13108 ***/ 13109 'clamp': function(el) { 13110 return rangeClamp(this, el); 13111 } 13112 13113 }); 13114 13115 13116 /*** @namespace Number ***/ 13117 13118 defineStatic(sugarNumber, { 13119 13120 /*** 13121 * @method range([start], [end]) 13122 * @returns Range 13123 * @static 13124 * @short Creates a new number range between [start] and [end]. See `ranges` 13125 * for more. 13126 * 13127 * @example 13128 * 13129 * Number.range(5, 10) 13130 * Number.range(20, 15) 13131 * 13132 * @param {number} [start] 13133 * @param {number} [end] 13134 * 13135 ***/ 13136 'range': PrimitiveRangeConstructor 13137 13138 }); 13139 13140 defineInstance(sugarNumber, { 13141 13142 /*** 13143 * @method upto(num, [step] = 1, [fn]) 13144 * @returns Array 13145 * @short Returns an array containing numbers from the number up to `num`. 13146 * @extra Optionally calls [fn] for each number in that array. [step] allows 13147 * multiples other than 1. [fn] can be passed in place of [step]. 13148 * 13149 * @callback rangeEveryFn 13150 * 13151 * el The element of the current iteration. 13152 * i The index of the current iteration. 13153 * r A reference to the range. 13154 * 13155 * @example 13156 * 13157 * (2).upto(6) -> [2, 3, 4, 5, 6] 13158 * (2).upto(6, function(n) { 13159 * // This function is called 5 times receiving n as the value. 13160 * }); 13161 * (2).upto(8, 2) -> [2, 4, 6, 8] 13162 * 13163 * @signature upto(num, [fn]) 13164 * @param {number} num 13165 * @param {number} [step] 13166 * @param {rangeEveryFn} [fn] 13167 * @callbackParam {RangeElement} el 13168 * @callbackParam {number} i 13169 * @callbackParam {Range} r 13170 * 13171 ***/ 13172 'upto': function(n, num, step, fn) { 13173 return rangeEvery(new Range(n, num), step, false, fn); 13174 }, 13175 13176 /*** 13177 * @method clamp([start] = Infinity, [end] = Infinity) 13178 * @returns Number 13179 * @short Constrains the number so that it falls on or between [start] and 13180 * [end]. 13181 * @extra This will build a range object that has an equivalent `clamp` method. 13182 * 13183 * @example 13184 * 13185 * (3).clamp(50, 100) -> 50 13186 * (85).clamp(50, 100) -> 85 13187 * 13188 * @param {number} [start] 13189 * @param {number} [end] 13190 * 13191 ***/ 13192 'clamp': function(n, start, end) { 13193 return rangeClamp(new Range(start, end), n); 13194 }, 13195 13196 /*** 13197 * @method cap([max] = Infinity) 13198 * @returns Number 13199 * @short Constrains the number so that it is no greater than [max]. 13200 * @extra This will build a range object that has an equivalent `cap` method. 13201 * 13202 * @example 13203 * 13204 * (100).cap(80) -> 80 13205 * 13206 * @param {number} [max] 13207 * 13208 ***/ 13209 'cap': function(n, max) { 13210 return rangeClamp(new Range(undefined, max), n); 13211 } 13212 13213 });
13214 13215 /*** 13216 * @method downto(num, [step] = 1, [fn]) 13217 * @returns Array 13218 * @short Returns an array containing numbers from the number down to `num`. 13219 * @extra Optionally calls [fn] for each number in that array. [step] allows 13220 * multiples other than 1. [fn] can be passed in place of [step]. 13221 * 13222 * @callback rangeEveryFn 13223 * 13224 * el The element of the current iteration. 13225 * i The index of the current iteration. 13226 * r A reference to the range. 13227 * 13228 * @example 13229 * 13230 * (8).downto(3) -> [8, 7, 6, 5, 4, 3] 13231 * (8).downto(3, function(n) { 13232 * // This function is called 6 times receiving n as the value. 13233 * }); 13234 * (8).downto(2, 2) -> [8, 6, 4, 2] 13235 * 13236 * @signature upto(num, [fn]) 13237 * @param {number} num 13238 * @param {number} [step] 13239 * @param {rangeEveryFn} [fn] 13240 * @callbackParam {RangeElement} el 13241 * @callbackParam {number} i 13242 * @callbackParam {Range} r 13243 * 13244 ***/ 13245 alias(sugarNumber, 'downto', 'upto'); 13246 13247 13248 /*** @namespace String ***/ 13249 13250 defineStatic(sugarString, { 13251 13252 /*** 13253 * @method range([start], [end]) 13254 * @returns Range 13255 * @static 13256 * @short Creates a new string range between [start] and [end]. See `ranges` 13257 * for more. 13258 * 13259 * @example 13260 * 13261 * String.range('a', 'z') 13262 * String.range('t', 'm') 13263 * 13264 * @param {string} [start] 13265 * @param {string} [end] 13266 * 13267 ***/ 13268 'range': PrimitiveRangeConstructor 13269 13270 }); 13271 13272 13273 /*** @namespace Date ***/ 13274 13275 13276 var FULL_CAPTURED_DURATION = '((?:\\d+)?\\s*(?:' + DURATION_UNITS + '))s?'; 13277 13278 // Duration text formats 13279 var RANGE_REG_FROM_TO = /(?:from)?\s*(.+)\s+(?:to|until)\s+(.+)$/i, 13280 RANGE_REG_REAR_DURATION = RegExp('(.+)\\s*for\\s*' + FULL_CAPTURED_DURATION, 'i'), 13281 RANGE_REG_FRONT_DURATION = RegExp('(?:for)?\\s*' + FULL_CAPTURED_DURATION + '\\s*(?:starting)?\\s*at\\s*(.+)', 'i'); 13282 13283 var DateRangeConstructor = function(start, end) { 13284 if (arguments.length === 1 && isString(start)) { 13285 return createDateRangeFromString(start); 13286 } 13287 return new Range(getDateForRange(start), getDateForRange(end)); 13288 }; 13289 13290 function createDateRangeFromString(str) { 13291 var match, datetime, duration, dio, start, end; 13292 if (sugarDate.get && (match = str.match(RANGE_REG_FROM_TO))) { 13293 start = getDateForRange(match[1].replace('from', 'at')); 13294 end = sugarDate.get(start, match[2]); 13295 return new Range(start, end); 13296 } 13297 if (match = str.match(RANGE_REG_FRONT_DURATION)) { 13298 duration = match[1]; 13299 datetime = match[2]; 13300 } 13301 if (match = str.match(RANGE_REG_REAR_DURATION)) { 13302 datetime = match[1]; 13303 duration = match[2]; 13304 } 13305 if (datetime && duration) { 13306 start = getDateForRange(datetime); 13307 dio = getDateIncrementObject(duration); 13308 end = incrementDate(start, dio[0], dio[1]); 13309 } else { 13310 start = str; 13311 } 13312 return new Range(getDateForRange(start), getDateForRange(end)); 13313 } 13314 13315 function getDateForRange(d) { 13316 if (isDate(d)) { 13317 return d; 13318 } else if (d == null) { 13319 return new Date(); 13320 } else if (sugarDate.create) { 13321 return sugarDate.create(d); 13322 } 13323 return new Date(d); 13324 } 13325 13326 /*** 13327 * @method [dateUnit]() 13328 * @returns Number 13329 * @namespace Range 13330 * @short Returns the span of a date range in the given unit. 13331 * @extra Higher order units ("days" and greater) walk the date to avoid 13332 * discrepancies with ambiguity. Lower order units simply subtract the 13333 * start from the end. 13334 * 13335 * @set 13336 * milliseconds 13337 * seconds 13338 * minutes 13339 * hours 13340 * days 13341 * weeks 13342 * months 13343 * years 13344 * 13345 * @example 13346 * 13347 * janToMay.months() -> 4 13348 * janToMay.days() -> 121 13349 * janToMay.hours() -> 2904 13350 * janToMay.minutes() -> 220320 13351 * 13352 ***/ 13353 function buildDateRangeUnits() { 13354 var methods = {};
13355 forEach(DURATION_UNITS.split('|'), function(unit, i) { 13356 var name = unit + 's', mult, fn; 13357 if (i < 4) { 13358 fn = function() { 13359 return rangeEvery(this, unit, true); 13360 }; 13361 } else { 13362 mult = MULTIPLIERS[simpleCapitalize(name)]; 13363 fn = function() { 13364 return trunc((this.end - this.start) / mult); 13365 }; 13366 } 13367 methods[name] = fn; 13368 }); 13369 defineOnPrototype(Range, methods); 13370 } 13371 13372 defineStatic(sugarDate, { 13373 13374 /*** 13375 * @method range([start], [end]) 13376 * @returns Range 13377 * @namespace Date 13378 * @static 13379 * @short Creates a new date range between [start] and [end]. 13380 * @extra Arguments may be either dates or strings which will be forwarded to 13381 * the date constructor (`create` will be used if present in the build). 13382 * If either [start] or [end] are undefined, they will default to the 13383 * current date. This method also accepts an alternate syntax of a
13384 * single string describing the range in natural language. See `ranges` 13385 * for more. 13386 * 13387 * @example 13388 * 13389 * Date.range(jan, may) 13390 * Date.range('today', 'tomorrow') 13391 * Date.range('now', '5 days ago') 13392 * Date.range('last Monday') 13393 * Date.range('Monday to Friday') 13394 * Date.range('tomorrow from 3pm to 5pm') 13395 * Date.range('1 hour starting at 5pm Tuesday') 13396 * 13397 * @param {string|Date} [start] 13398 * @param {string|Date} [end] 13399 * 13400 ***/ 13401 'range': DateRangeConstructor 13402 13403 }); 13404 13405 buildDateRangeUnits(); 13406 13407}).call(this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.