diff --git a/.npmignore b/.npmignore index 4a44df3f34..08210e2d72 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,7 @@ .* *.custom.* *.d.ts +CONTRIBUTING.md doc/*.php node_modules/ perf/*.html @@ -12,8 +13,8 @@ test/*.sh vendor/*.gz vendor/backbone/ vendor/benchmark.js/*.jar -vendor/closure-compiler -vendor/docdown +vendor/closure-compiler/ +vendor/docdown/ vendor/firebug-lite/ vendor/json3/ vendor/jquery/ @@ -21,5 +22,5 @@ vendor/qunit/qunit/*.css vendor/qunit/qunit/*-1.8.0.js vendor/requirejs/ vendor/underscore/*-min.js -vendor/uglifyjs +vendor/uglifyjs/ vendor/underscore/test/ diff --git a/.travis.yml b/.travis.yml index 2b22b6adb1..86f5e53819 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,20 @@ language: node_js node_js: - 0.6 - - 0.8 + - 0.9 +env: + - TEST_COMMAND="phantomjs ./test/test.js ../dist/lodash.compat.js" + - TEST_COMMAND="phantomjs ./test/test.js ../dist/lodash.compat.min.js" + - TEST_COMMAND="node ./test/test.js ../dist/lodash.js" + - TEST_COMMAND="node ./test/test.js ../dist/lodash.min.js" + - TEST_COMMAND="node ./test/test-build.js --time-limit 49m40s" +git: + depth: 1 +branches: + only: + - master before_script: - - "curl -H 'Accept: application/vnd.github.v3.raw' https://api.github.com/repos/bestiejs/lodash/git/blobs/a2787b470c577cee2404d186c562dd9835f779f5 | tar xvz -C vendor" - - "curl -H 'Accept: application/vnd.github.v3.raw' https://api.github.com/repos/bestiejs/lodash/git/blobs/7ecae09d413eb48dd5785fe877f24e60ac3bbcef | tar xvz -C vendor" + - "tar -xzvf vendor/closure-compiler.tar.gz -C vendor" + - "tar -xzvf vendor/uglifyjs.tar.gz -C vendor" +script: + $TEST_COMMAND diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca4191ac06..f2eb92f587 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,12 +7,12 @@ Please make sure to [search the issue tracker](https://github.com/bestiejs/lodas Include updated unit tests in the `test` directory as part of your pull request. You can run the tests from the command line via `npm test`, or open `test/index.html` in a web browser. -The `test/run-test.sh` script attempts to run the tests in [Rhino](http://www.mozilla.org/rhino/), [RingoJS](http://ringojs.org/), [Narwhal](https://github.com/280north/narwhal), and [Node](http://nodejs.org/), before running them in your default browser. +The `test/run-test.sh` script attempts to run the tests in [Rhino](https://developer.mozilla.org/en-US/docs/Rhino), [RingoJS](http://ringojs.org/), [Narwhal](https://github.com/280north/narwhal), and [Node](http://nodejs.org/), before running them in your default browser. The [Backbone](http://backbonejs.org/) and [Underscore](http://http://underscorejs.org/) test suites are included as well. ## Contributor License Agreement -Lo-Dash is preparing to join [Dojo Foundation](http://dojofoundation.org/). +Lo-Dash is a member of the [Dojo Foundation](http://dojofoundation.org/). As such, we request that all contributors sign the Dojo Foundation [contributor license agreement](http://dojofoundation.org/about/claForm). For more information about CLAs, please check out Alex Russell’s excellent post, ["Why Do I Need to Sign This?"](http://infrequently.org/2008/06/why-do-i-need-to-sign-this/). diff --git a/LICENSE.txt b/LICENSE.txt index cd3ae1777d..cc082396c7 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ -Copyright 2012 John-David Dalton -Based on Underscore.js 1.4.3, copyright 2009-2012 Jeremy Ashkenas, -DocumentCloud Inc. +Copyright 2012-2013 The Dojo Foundation +Based on Underscore.js 1.4.3, copyright 2009-2013 Jeremy Ashkenas, +DocumentCloud Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 9d6c4893b8..28d4dfc1fd 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,29 @@ -# Lo-Dash v1.0.0-rc.3 +# Lo-Dash v1.0.0 [![build status](https://secure.travis-ci.org/bestiejs/lodash.png)](http://travis-ci.org/bestiejs/lodash) -An alternative to Underscore.js, delivering [consistency](https://github.com/bestiejs/lodash#resolved-underscorejs-issues), [customization](https://github.com/bestiejs/lodash#custom-builds), [performance](http://lodash.com/benchmarks), and [extra features](https://github.com/bestiejs/lodash#features). +An alternative to Underscore.js, delivering consistency, [customization](https://github.com/bestiejs/lodash#custom-builds), [performance](http://lodash.com/benchmarks), and [extra features](https://github.com/bestiejs/lodash#features). ## Download -* Lo-Dash builds:
-[Development](https://raw.github.com/bestiejs/lodash/v1.0.0-rc.3/lodash.js) and -[Production](https://raw.github.com/bestiejs/lodash/v1.0.0-rc.3/lodash.min.js) +* Lo-Dash builds (for modern environments):
+[Development](https://raw.github.com/bestiejs/lodash/v1.0.0/dist/lodash.js) and +[Production](https://raw.github.com/bestiejs/lodash/v1.0.0/dist/lodash.min.js) + +* Lo-Dash compatibility builds (for legacy and modern environments):
+[Development](https://raw.github.com/bestiejs/lodash/v1.0.0/dist/lodash.compat.js) and +[Production](https://raw.github.com/bestiejs/lodash/v1.0.0/dist/lodash.compat.min.js) * Underscore compatibility builds:
-[Development](https://raw.github.com/bestiejs/lodash/v1.0.0-rc.3/lodash.underscore.js) and -[Production](https://raw.github.com/bestiejs/lodash/v1.0.0-rc.3/lodash.underscore.min.js) +[Development](https://raw.github.com/bestiejs/lodash/v1.0.0/dist/lodash.underscore.js) and +[Production](https://raw.github.com/bestiejs/lodash/v1.0.0/dist/lodash.underscore.min.js) -* CDN copies of ≤ v1.0.0-rc.3’s builds are available on [cdnjs](http://cdnjs.com/) thanks to [CloudFlare](http://www.cloudflare.com/):
-[Lo-Dash dev](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.0-rc.3/lodash.js), -[Lo-Dash prod](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.0-rc.3/lodash.min.js), -[Underscore compat-dev](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.0-rc.3/lodash.underscore.js), and -[Underscore compat-prod](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.0-rc.3/lodash.underscore.min.js) +* CDN copies of ≤ v1.0.0’s builds are available on [cdnjs](http://cdnjs.com/) thanks to [CloudFlare](http://www.cloudflare.com/):
+[Lo-Dash dev](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.0/lodash.js), +[Lo-Dash prod](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.0/lodash.min.js),
+[Lo-Dash compat-dev](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.0/lodash.compat.js), +[Lo-Dash compat-prod](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.0/lodash.compat.min.js),
+[Underscore compat-dev](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.0/lodash.underscore.js), and +[Underscore compat-prod](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.0.0/lodash.underscore.min.js) * For optimal file size, [create a custom build](https://github.com/bestiejs/lodash#custom-builds) with only the features you need @@ -42,23 +48,28 @@ For more information check out these screencasts over Lo-Dash: * AMD loader support ([RequireJS](http://requirejs.org/), [curl.js](https://github.com/cujojs/curl), etc.) * [_(…)](http://lodash.com/docs#_) supports intuitive chaining + * [_.at](http://lodash.com/docs#at) for cherry-picking collection values * [_.bindKey](http://lodash.com/docs#bindKey) for binding [*“lazy”* defined](http://michaux.ca/articles/lazy-function-definition-pattern) methods - * [_.cloneDeep](http://lodash.com/docs#cloneDeep) for *“deep”* cloning arrays and objects + * [_.cloneDeep](http://lodash.com/docs#cloneDeep) for deep cloning arrays and objects * [_.contains](http://lodash.com/docs#contains) accepts a `fromIndex` argument * [_.forEach](http://lodash.com/docs#forEach) is chainable and supports exiting iteration early * [_.forIn](http://lodash.com/docs#forIn) for iterating over an object’s own and inherited properties * [_.forOwn](http://lodash.com/docs#forOwn) for iterating over an object’s own properties * [_.isPlainObject](http://lodash.com/docs#isPlainObject) checks if values are created by the `Object` constructor - * [_.merge](http://lodash.com/docs#merge) for a *“deep”* [_.extend](http://lodash.com/docs#extend) - * [_.partial](http://lodash.com/docs#partial) for partial application without `this` binding - * [_.pick](http://lodash.com/docs#pick) and [_.omit](http://lodash.com/docs#omit) accepts `callback` and `thisArg` arguments - * [_.template](http://lodash.com/docs#template) supports [ES6 template delimiters](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-7.8.6) and utilizes [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) for easier debugging + * [_.merge](http://lodash.com/docs#merge) for a deep [_.extend](http://lodash.com/docs#extend) + * [_.partial](http://lodash.com/docs#partial) and [_.partialRight](http://lodash.com/docs#partialRight) for partial application without `this` binding + * [_.template](http://lodash.com/docs#template) supports [*“imports”* options](http://lodash.com/docs#templateSettings_imports), [ES6 template delimiters](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-7.8.6), and [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) + * [_.where](http://lodash.com/docs#where) supports deep object comparisons + * [_.clone](http://lodash.com/docs#clone), [_.omit](http://lodash.com/docs#omit), [_.pick](http://lodash.com/docs#pick), + [and more…](http://lodash.com/docs "_.assign, _.cloneDeep, _.first, _.initial, _.isEqual, _.last, _.merge, _.rest") accept `callback` and `thisArg` arguments * [_.contains](http://lodash.com/docs#contains), [_.size](http://lodash.com/docs#size), [_.toArray](http://lodash.com/docs#toArray), - [and more…](http://lodash.com/docs "_.countBy, _.every, _.filter, _.find, _.forEach, _.groupBy, _.invoke, _.map, _.max, _.min, _.pluck, _.reduce, _.reduceRight, _.reject, _.shuffle, _.some, _.sortBy, _.where") accept strings + [and more…](http://lodash.com/docs "_.at, _.countBy, _.every, _.filter, _.find, _.forEach, _.groupBy, _.invoke, _.map, _.max, _.min, _.pluck, _.reduce, _.reduceRight, _.reject, _.shuffle, _.some, _.sortBy, _.where") accept strings + * [_.filter](http://lodash.com/docs#filter), [_.find](http://lodash.com/docs#find), [_.map](http://lodash.com/docs#map), + [and more…](http://lodash.com/docs "_.countBy, _.every, _.first, _.groupBy, _.initial, _.last, _.max, _.min, _.reject, _.rest, _.some, _.sortBy, _.sortedIndex, _.uniq") support *“_.pluck”* and *“_.where”* `callback` shorthands ## Support -Lo-Dash has been tested in at least Chrome 5~23, Firefox 1~17, IE 6-10, Opera 9.25-12, Safari 3-6, Node.js 0.4.8-0.8.16, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5. +Lo-Dash has been tested in at least Chrome 5~24, Firefox 1~18, IE 6-10, Opera 9.25-12, Safari 3-6, Node.js 0.4.8-0.8.19, Narwhal 0.3.2, PhantomJS 1.8.1, RingoJS 0.9, and Rhino 1.7RC5. ## Custom builds @@ -70,18 +81,23 @@ To top it off, we handle all method dependency and alias mapping for you. lodash backbone ``` - * CSP builds, supporting default [Content Security Policy](http://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html) restrictions, may be created using the `csp` modifier argument. - The `csp` modifier is an alais of the `mobile` modifier. Chrome extensions will require [sandboxing](http://developer.chrome.com/trunk/extensions/sandboxingEval.html) or the use of either the `csp`, `mobile`, or `underscore` build. + * CSP builds, supporting default [Content Security Policy](https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html) restrictions, may be created using the `csp` modifier argument. + The `csp` modifier is an alais of the `mobile` modifier. Lo-Dash may be used in Chrome extensions by using either the `csp`, `mobile`, or `underscore` build and using precompiled templates, or loading Lo-Dash in a [sandbox](http://developer.chrome.com/stable/extensions/sandboxingEval.html). ```bash lodash csp ``` - * Legacy builds, tailored for older browsers without [ES5 support](http://es5.github.com/), may be created using the `legacy` modifier argument. + * Legacy builds, tailored for older environments without [ES5 support](http://es5.github.com/), may be created using the `legacy` modifier argument. ```bash lodash legacy ``` - * Mobile builds, with IE < 9 bug fixes and method compilation removed, may be created using the `mobile` modifier argument. + * Modern builds, tailored for newer environments with ES5 support, may be created using the `modern` modifier argument. +```bash +lodash modern +``` + + * Mobile builds, without method compilation and most bug fixes for old browsers, may be created using the `mobile` modifier argument. ```bash lodash mobile ``` @@ -142,7 +158,7 @@ lodash template="./*.jst" * Use the `settings` argument to pass the template settings used when precompiling templates. ```bash -lodash settings="{interpolate:/\\{\\{([\\s\\S]+?)\\}\\}/g}" +lodash settings="{interpolate:/\{\{([\s\S]+?)\}\}/g}" ``` * Use the `moduleId` argument to specify the AMD module ID of Lo-Dash, which defaults to “lodash”, used by precompiled templates. @@ -150,18 +166,19 @@ lodash settings="{interpolate:/\\{\\{([\\s\\S]+?)\\}\\}/g}" lodash moduleId="underscore" ``` -All arguments, except `legacy` with `csp` or `mobile`, may be combined.
+All arguments, except `legacy` with `csp`, `mobile`, `modern`, or `underscore`, may be combined.
Unless specified by `-o` or `--output`, all files created are saved to the current working directory. The following options are also supported: - * `-c`, `--stdout`     Write output to standard output - * `-d`, `--debug`       Write only the debug output - * `-h`, `--help`         Display help information - * `-m`, `--minify`     Write only the minified output - * `-o`, `--output`     Write output to a given path/filename - * `-s`, `--silent`     Skip status updates normally logged to the console - * `-V`, `--version`   Output current version of Lo-Dash + * `-c`, `--stdout` ......... Write output to standard output + * `-d`, `--debug` ........... Write only the non-minified development output + * `-h`, `--help` ............. Display help information + * `-m`, `--minify` ......... Write only the minified production output + * `-o`, `--output` ......... Write output to a given path/filename + * `-p`, `--source-map` .. Generate a source map for the minified output + * `-s`, `--silent` ......... Skip status updates normally logged to the console + * `-V`, `--version` ....... Output current version of Lo-Dash The `lodash` command-line utility is available when Lo-Dash is installed as a global package (i.e. `npm install -g lodash`). @@ -224,61 +241,32 @@ require({ }); ``` -## Resolved Underscore.js issues - - * Allow iteration of objects with a `length` property [[#799](https://github.com/documentcloud/underscore/pull/799), [test](https://github.com/bestiejs/lodash/blob/v1.0.0-rc.3/test/test.js#L605-L611)] - * Fix cross-browser object iteration bugs [[#60](https://github.com/documentcloud/underscore/issues/60), [#376](https://github.com/documentcloud/underscore/issues/376), [test](https://github.com/bestiejs/lodash/blob/v1.0.0-rc.3/test/test.js#L618-L642)] - * Methods should work on pages with incorrectly shimmed native methods [[#7](https://github.com/documentcloud/underscore/issues/7), [#742](https://github.com/documentcloud/underscore/issues/742), [test](https://github.com/bestiejs/lodash/blob/v1.0.0-rc.3/test/test.js#L140-L146)] - * `_.isEmpty` should support jQuery/MooTools DOM query collections [[#690](https://github.com/documentcloud/underscore/pull/690), [test](https://github.com/bestiejs/lodash/blob/v1.0.0-rc.3/test/test.js#L807-L812)] - * `_.isObject` should avoid V8 bug [#2291](http://code.google.com/p/v8/issues/detail?id=2291) [[#605](https://github.com/documentcloud/underscore/issues/605), [test](https://github.com/bestiejs/lodash/blob/v1.0.0-rc.3/test/test.js#L888-L900)] - * `_.keys` should work with `arguments` objects cross-browser [[#396](https://github.com/documentcloud/underscore/issues/396), [test](https://github.com/bestiejs/lodash/blob/v1.0.0-rc.3/test/test.js#L982-L984)] - * `_.range` should coerce arguments to numbers [[#634](https://github.com/documentcloud/underscore/issues/634), [#683](https://github.com/documentcloud/underscore/issues/683), [test](https://github.com/bestiejs/lodash/blob/v1.0.0-rc.3/test/test.js#L1383-L1386)] - ## Release Notes -### v1.0.0-rc.3 - -#### Compatibility Warnings - - * Made `_#join`, `_#pop`, and `_#shift` wrapper methods return unwrapped values - * Made *“Functions”* methods wrapper counterparts return wrapped values - * Removed `_.chain` and `_#chain` methods - -#### Changes - - * Added [_.cloneDeep](http://lodash.com/docs#cloneDeep) alias of `_.clone(…, true)` - * Added `_.once` to the `backbone` build method dependencies - * Ensured `backbone` builds implement Underscore’s chaining behavior - * Ensured the `settings=…` build option doesn’t clobber the default `moduleId` - * Ensured Lo-Dash’s `npm` package installation avoids erroring when no other modules have been globally installed - * Made compiled templates loaded via AMD use the Lo-Dash module for their `_` references - * Removed the *“Collections”* method `_.forEach` dependency from *“Arrays”* method `_.intersection` - * Optimized `_.isArray` and `_.isFunction` fallbacks as well as
- `_.intersection`, `_.isDate`, `_.isRegExp`, `_.reduce`, `_.reduceRight`, `_.union`, and `_.uniq` - -### v1.0.0-rc.2 - - * Specified more method chaining behaviors - * Updated `underscore` build compatibility to v1.4.3 - -### v1.0.0-rc.1 +### v1.0.0 #### Compatibility Warnings - * Made `_(…)` intuitively chain without needing to call `_#chain` - * Made `_.isEqual` equate `arguments` objects to similar `Object` objects - * Made `_.clone` copy the enumerable properties of `arguments` objects and objects
- created by constructors other than `Object` are cloned to plain `Object` objects + * Made `_.defaults` preserve `null` values, instead of overwriting them #### Changes - * Ensure Lo-Dash runs in the JS engine embedded in Adobe products - * Ensured `_.reduce` and `_.reduceRight` pass the correct number of `callback` arguments - * Ensured `_.throttle` nulls the `timeoutId` - * Made deep `_.clone` more closely follow the structured clone algorithm and copy array properties assigned by `RegExp#exec` - * Optimized compiled templates in Firefox - * Optimized `_.forEach`, `_.forOwn`, `_.isNumber`, and `_.isString` - * Simplified `iteratorTemplate` + * Added [_.at](http://lodash.com/docs#at) and [_.partialRight](http://lodash.com/docs#partialRight) + * Added [*“imports”*](http://lodash.com/docs#templateSettings_imports) option to `_.templateSettings` + * Added `modern` and `--source-map`/`-p` build options + * Added support for *“_.pluck”* and *“_.where”* `callback` shorthands + * Ensured `_.assign` and `_.defaults` support arrays + * Ensured `_.merge` assigns `null` values and produces dense arrays + * Deferred minifier downloads until the `lodash` utility requires them + * Flipped `noNodeClass` test to avoid triggering Firebug’s *“break on all errors”* feature + * Made `_.where` support deep object comparisons + * Optimized `_.invert`, `_.pairs`, and `_.values` + * Reduced `_.max`, `_.min`, `_.pluck`, `_.toArray`, and `_.where` + * Simplified `createIterator` and `iteratorTemplate` + * Tweaked `_.uniqueId` to avoid problems with buggy minifiers + * Updated `underscore` build compatibility to v1.4.4 + * Added support for `callback` and `thisArg` arguments to `_.assign`, `_.clone`,
+ `_.cloneDeep`, `_.first`, `_.last`, `_.initial`, `_.isEqual`, `_.merge`, and `_.rest` The full changelog is available [here](https://github.com/bestiejs/lodash/wiki/Changelog). diff --git a/build.js b/build.js index 899d190a8b..e38a3f1aa5 100755 --- a/build.js +++ b/build.js @@ -66,7 +66,8 @@ /** Used to track function dependencies */ var dependencyMap = { 'after': [], - 'assign': ['isArguments'], + 'assign': ['isArray', 'forEach', 'forOwn'], + 'at': ['isString'], 'bind': ['isFunction', 'isObject'], 'bindAll': ['bind', 'functions'], 'bindKey': ['isFunction', 'isObject'], @@ -75,29 +76,29 @@ 'compact': [], 'compose': [], 'contains': ['indexOf', 'isString'], - 'countBy': ['forEach'], + 'countBy': ['forEach', 'identity', 'isEqual', 'keys'], 'debounce': [], - 'defaults': ['isArguments'], + 'defaults': ['isArray', 'forEach', 'forOwn'], 'defer': [], 'delay': [], 'difference': ['indexOf'], 'escape': [], - 'every': ['isArray'], - 'filter': ['isArray'], - 'find': ['forEach'], + 'every': ['identity', 'isArray', 'isEqual', 'keys'], + 'filter': ['identity', 'isArray', 'isEqual', 'keys'], + 'find': ['forEach', 'identity', 'isEqual', 'keys'], 'first': [], 'flatten': ['isArray'], 'forEach': ['identity', 'isArguments', 'isArray', 'isString'], 'forIn': ['identity', 'isArguments'], 'forOwn': ['identity', 'isArguments'], 'functions': ['forIn', 'isFunction'], - 'groupBy': ['forEach'], + 'groupBy': ['forEach', 'identity', 'isEqual', 'keys'], 'has': [], 'identity': [], 'indexOf': ['sortedIndex'], 'initial': [], 'intersection': ['indexOf'], - 'invert': ['forOwn'], + 'invert': ['keys'], 'invoke': ['forEach'], 'isArguments': [], 'isArray': [], @@ -119,67 +120,69 @@ 'keys': ['forOwn', 'isArguments', 'isObject'], 'last': [], 'lastIndexOf': [], - 'map': ['isArray'], - 'max': ['isArray', 'isString'], + 'map': ['identity', 'isArray', 'isEqual', 'keys'], + 'max': ['isArray', 'isEqual', 'isString', 'keys'], 'memoize': [], - 'merge': ['forOwn', 'isArray', 'isPlainObject'], - 'min': ['isArray', 'isString'], + 'merge': ['forEach', 'forOwn', 'isArray', 'isObject', 'isPlainObject'], + 'min': ['isArray', 'isEqual', 'isString', 'keys'], 'mixin': ['forEach', 'forOwn', 'functions'], 'noConflict': [], 'object': [], 'omit': ['forIn', 'indexOf'], 'once': [], - 'pairs': ['forOwn'], + 'pairs': ['keys'], 'partial': ['isFunction', 'isObject'], - 'pick': ['forIn'], + 'partialRight': ['isFunction', 'isObject'], + 'pick': ['forIn', 'isObject'], 'pluck': ['map'], 'random': [], 'range': [], - 'reduce': ['isArray'], - 'reduceRight': ['forEach', 'isString', 'keys'], - 'reject': ['filter'], + 'reduce': ['identity', 'isArray', 'isEqual', 'keys'], + 'reduceRight': ['forEach', 'identity', 'isEqual', 'isString', 'keys'], + 'reject': ['filter', 'identity', 'isEqual', 'keys'], 'rest': [], 'result': ['isFunction'], 'shuffle': ['forEach'], 'size': ['keys'], - 'some': ['isArray'], - 'sortBy': ['forEach'], - 'sortedIndex': ['identity'], + 'some': ['identity', 'isArray', 'isEqual', 'keys'], + 'sortBy': ['forEach', 'identity', 'isEqual', 'keys'], + 'sortedIndex': ['identity', 'isEqual', 'keys'], 'tap': ['mixin'], - 'template': ['escape'], + 'template': ['defaults', 'escape', 'keys', 'values'], 'throttle': [], 'times': [], 'toArray': ['isString', 'values'], 'unescape': [], 'union': ['uniq'], - 'uniq': ['identity', 'indexOf'], + 'uniq': ['indexOf', 'isEqual', 'keys'], 'uniqueId': [], 'value': ['mixin'], - 'values': ['forOwn'], - 'where': ['filter', 'keys'], + 'values': ['keys'], + 'where': ['filter'], 'without': ['indexOf'], 'wrap': [], 'zip': ['max', 'pluck'], // method used by the `backbone` and `underscore` builds - 'chain': ['mixin'] + 'chain': ['mixin'], + 'findWhere': ['where'] }; /** Used to inline `iteratorTemplate` */ var iteratorOptions = [ 'args', - 'arrayLoop', + 'arrays', 'bottom', 'firstArg', 'hasDontEnumBug', + 'hasEnumPrototype', 'isKeysFast', - 'objectLoop', + 'loop', 'nonEnumArgs', 'noCharByIndex', 'shadowed', 'top', - 'useHas', - 'useStrict' + 'useHas' ]; /** List of all Lo-Dash methods */ @@ -240,13 +243,14 @@ /** List of methods used by Underscore */ var underscoreMethods = _.without.apply(_, [allMethods].concat([ + 'at', 'bindKey', 'cloneDeep', 'forIn', 'forOwn', 'isPlainObject', 'merge', - 'partial' + 'partialRight' ])); /** List of ways to export the `lodash` function */ @@ -361,6 +365,8 @@ return match.replace(/^( *)return new lodash.+/m, function() { var indent = arguments[1]; return indent + [ + '', + 'var result = func.apply(lodash, args);', 'if (this.__chain__) {', ' result = new lodash(result);', ' result.__chain__ = true;', @@ -418,12 +424,7 @@ * @returns {String} Returns the modified source. */ function addCommandsToHeader(source, commands) { - return source.replace(/(\/\*!\n)( \*)?( *Lo-Dash [\w.-]+)(.*)/, function() { - // convert unmatched groups to empty strings - var parts = _.map(slice.call(arguments, 1), function(part) { - return part || ''; - }); - + return source.replace(/(\/\**\n)( \*)( *@license[\s*]+)( *Lo-Dash [\w.-]+)(.*)/, function() { // remove `node path/to/build.js` from `commands` if (commands[0] == 'node') { commands.splice(0, 2); @@ -435,12 +436,20 @@ var pair = command.split(separator); command = pair[0] + separator + '"' + pair[1] + '"'; } + // escape newlines, carriage returns, multi-line comment end tokens + command = command + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/\*\//g, '*\\/'); + return command; }); // add build commands to copyright/license header + var parts = slice.call(arguments, 1); return ( parts[0] + - parts[1] + parts[2] + ' (Custom Build)' + parts[3] + '\n' + + parts[1] + + parts[2] + parts[3] + ' (Custom Build)' + parts[4] + '\n' + parts[1] + ' Build: `lodash ' + commands.join(' ') + '`' ); }); @@ -510,6 +519,17 @@ return source.join('\n'); } + /** + * Capitalizes a given string. + * + * @private + * @param {String} string The string to capitalize. + * @returns {String} Returns the capitalized string. + */ + function capitalize(string) { + return string[0].toUpperCase() + string.toLowerCase().slice(1); + } + /** * Removes unnecessary comments, whitespace, and pseudo private properties. * @@ -543,8 +563,9 @@ '', ' lodash backbone Build with only methods required by Backbone', ' lodash csp Build supporting default Content Security Policy restrictions', - ' lodash legacy Build tailored for older browsers without ES5 support', - ' lodash mobile Build with IE < 9 bug fixes & method compilation removed', + ' lodash legacy Build tailored for older environments without ES5 support', + ' lodash modern Build tailored for newer environments with ES5 support', + ' lodash mobile Build without method compilation and most bug fixes for old browsers', ' lodash strict Build with `_.assign`, `_.bindAll`, & `_.defaults` in strict mode', ' lodash underscore Build tailored for projects already using Underscore', ' lodash include=... Comma separated method/category names to include in the build', @@ -560,21 +581,22 @@ ' lodash template=... File path pattern used to match template files to precompile', ' (e.g. `lodash template=./*.jst`)', ' lodash settings=... Template settings used when precompiling templates', - ' (e.g. `lodash settings="{interpolate:/\\\\{\\\\{([\\\\s\\\\S]+?)\\\\}\\\\}/g}"`)', + ' (e.g. `lodash settings="{interpolate:/{{([\\s\\S]+?)}}/g}"`)', ' lodash moduleId=... The AMD module ID of Lo-Dash, which defaults to “lodash”, used by precompiled templates', '', - ' All arguments, except `legacy` with `csp` or `mobile`, may be combined.', + ' All arguments, except `legacy` with `csp`, `mobile`, `modern`, or `underscore`, may be combined.', ' Unless specified by `-o` or `--output`, all files created are saved to the current working directory.', '', ' Options:', '', - ' -c, --stdout Write output to standard output', - ' -d, --debug Write only the debug output', - ' -h, --help Display help information', - ' -m, --minify Write only the minified output', - ' -o, --output Write output to a given path/filename', - ' -s, --silent Skip status updates normally logged to the console', - ' -V, --version Output current version of Lo-Dash', + ' -c, --stdout Write output to standard output', + ' -d, --debug Write only the non-minified development output', + ' -h, --help Display help information', + ' -m, --minify Write only the minified production output', + ' -o, --output Write output to a given path/filename', + ' -p, --source-map Generate a source map for the minified output', + ' -s, --silent Skip status updates normally logged to the console', + ' -V, --version Output current version of Lo-Dash', '' ].join('\n')); } @@ -591,21 +613,46 @@ } /** - * Gets the Lo-Dash method assignments snippet from `source`. + * Gets the category of the given method name. * * @private * @param {String} source The source to inspect. - * @returns {String} Returns the method assignments snippet. + * @param {String} methodName The method name. + * @returns {String} Returns the method name's category. */ - function getMethodAssignments(source) { - return (source.match(/\/\*-+\*\/\n(?:\s*\/\/.*)*\s*lodash\.\w+ *=[\s\S]+?lodash\.VERSION *=.+/) || [''])[0]; + function getCategory(source, methodName) { + var result = /@category +(\w+)/.exec(matchFunction(source, methodName)); + return result ? result[1] : ''; + } + + /** + * Gets an array of category dependencies for a given category. + * + * @private + * @param {String} source The source to inspect. + * @param {String} category The category. + * @returns {Array} Returns an array of cetegory dependants. + */ + function getCategoryDependencies(source, category) { + var methods = _.uniq(getMethodsByCategory(source, category).reduce(function(result, methodName) { + push.apply(result, getDependencies(methodName)); + return result; + }, [])); + + var categories = _.uniq(methods.map(function(methodName) { + return getCategory(source, methodName); + })); + + return categories.filter(function(other) { + return other != category; + }); } /** * Gets an array of depenants for a method by a given name. * * @private - * @param {String} methodName The name of the method to query. + * @param {String} methodName The method name. * @returns {Array} Returns an array of method dependants. */ function getDependants(methodName) { @@ -656,7 +703,7 @@ return source.replace(/\n(?:.*)/g, function(match, index) { match = match.slice(1); return ( - match == '}' && source.indexOf('}', index + 2) == -1 ? '\n ' : '\n ' + match == '}' && source.indexOf('}', index + 2) < 0 ? '\n ' : '\n ' ) + match; }); } @@ -669,7 +716,7 @@ * @returns {String} Returns the `isArguments` fallback. */ function getIsArgumentsFallback(source) { - return (source.match(/(?:\s*\/\/.*)*\n( *)if *\(noArgsClass\)[\s\S]+?};\n\1}/) || [''])[0]; + return (source.match(/(?:\s*\/\/.*)*\n( *)if *\((?:noArgsClass|!isArguments)[\s\S]+?};\n\1}/) || [''])[0]; } /** @@ -694,6 +741,17 @@ return (source.match(/^( *)var iteratorTemplate *= *[\s\S]+?\n\1.+?;\n/m) || [''])[0]; } + /** + * Gets the Lo-Dash method assignments snippet from `source`. + * + * @private + * @param {String} source The source to inspect. + * @returns {String} Returns the method assignments snippet. + */ + function getMethodAssignments(source) { + return (source.match(/\/\*-+\*\/\n(?:\s*\/\/.*)*\s*lodash\.\w+ *=[\s\S]+?lodash\.VERSION *=.+/) || [''])[0]; + } + /** * Gets the names of methods in `source` belonging to the given `category`. * @@ -704,7 +762,7 @@ */ function getMethodsByCategory(source, category) { return allMethods.filter(function(methodName) { - return category && RegExp('@category ' + category + '\\b').test(matchFunction(source, methodName)); + return getCategory(source, methodName) == category; }); } @@ -747,18 +805,24 @@ // match multi-line comment block (could be on a single line) '(?:\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/\\n)?' + // begin non-capturing group - '(?:' + + '( *)(?:' + // match a function declaration - '( *)function ' + funcName + '\\b[\\s\\S]+?\\n\\1}|' + - // match a variable declaration with `createIterator` - ' +var ' + funcName + ' *=.*?createIterator\\((?:{|[a-zA-Z])[\\s\\S]+?\\);|' + + 'function ' + funcName + '\\b[\\s\\S]+?\\n\\1}|' + // match a variable declaration with function expression - '( *)var ' + funcName + ' *=.*?function[\\s\\S]+?\\n\\2};' + + 'var ' + funcName + ' *=.*?function[\\s\\S]+?\\n\\1};' + // end non-capturing group ')\\n' )); - return result ? result[0] : ''; + // match variables that are explicitly defined as functions + result || (result = source.match(RegExp( + // match multi-line comment block + '(?:\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/)?\\n' + + // match simple variable declarations and those with `createIterator` + ' *var ' + funcName + ' *=(?:.+?|.*?createIterator\\([\\s\\S]+?\\));\\n' + ))); + + return /@type +Function|function\s*\w*\(/.test(result) ? result[0] : ''; } /** @@ -797,33 +861,51 @@ } /** - * Removes the `createFunction` function from `source`. + * Removes all `argsAreObjects` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeCreateFunction(source) { - source = removeVar(source, 'isFirefox'); - return removeFunction(source, 'createFunction') - .replace(/createFunction/g, 'Function') - .replace(/(?:\s*\/\/.*)*\s*if *\(isIeOpera[^}]+}\n/, ''); + function removeArgsAreObjects(source) { + source = removeVar(source, 'argsAreObjects'); + + // remove `argsAreObjects` from `_.isArray` + source = source.replace(matchFunction(source, 'isArray'), function(match) { + return match.replace(/\(argsAreObjects && *([^)]+)\)/g, '$1'); + }); + + // remove `argsAreObjects` from `_.isEqual` + source = source.replace(matchFunction(source, 'isEqual'), function(match) { + return match.replace(/!argsAreObjects[^:]+:\s*/g, ''); + }); + + return source; } /** - * Removes the all references to `refName` from `createIterator` in `source`. + * Removes the all references to `varName` from `createIterator` in `source`. * * @private * @param {String} source The source to process. - * @param {String} refName The name of the reference to remove. + * @param {String} varName The name of the variable to remove. * @returns {String} Returns the modified source. */ - function removeFromCreateIterator(source, refName) { - var snippet = matchFunction(source, 'createIterator'); - if (snippet) { - // clip the snippet at the `factory` assignment - snippet = snippet.match(/Function\([\s\S]+$/)[0]; - var modified = snippet.replace(RegExp('\\b' + refName + '\\b,? *', 'g'), ''); + function removeFromCreateIterator(source, varName) { + var snippet = matchFunction(source, 'createIterator'); + if ( snippet) { + // remove data object property assignment + var modified = snippet.replace(RegExp("^ *'" + varName + "': *" + varName + '.+\\n', 'm'), ''); + source = source.replace(snippet, modified); + + // clip at the `factory` assignment + snippet = modified.match(/Function\([\s\S]+$/)[0]; + + modified = snippet + .replace(RegExp('\\b' + varName + '\\b,? *', 'g'), '') + .replace(/, *',/, "',") + .replace(/,\s*\)/, ')') + source = source.replace(snippet, modified); } return source; @@ -858,6 +940,61 @@ return removeFromCreateIterator(source, funcName); } + /** + * Removes all `hasDontEnumBug` references from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeHasDontEnumBug(source) { + source = removeFromCreateIterator(source, 'hasDontEnumBug'); + source = removeFromCreateIterator(source, 'shadowed'); + + // remove `hasDontEnumBug` declaration and assignment + source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasDontEnumBug\b.*|.+?hasDontEnumBug *=.+/g, ''); + + // remove `shadowed` variable + source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var shadowed[\s\S]+?;\n/, ''); + + // remove `hasDontEnumBug` from `iteratorTemplate` + source = source.replace(getIteratorTemplate(source), function(match) { + return match.replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(hasDontEnumBug[\s\S]+?["']\1<% *} *%>.+/, ''); + }); + + return source; + } + + /** + * Removes all `hasEnumPrototype` references from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeHasEnumPrototype(source) { + source = removeFromCreateIterator(source, 'hasEnumPrototype'); + + // remove `hasEnumPrototype` declaration and assignment + source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasEnumPrototype\b.*|.+?hasEnumPrototype *=.+/g, ''); + + // remove `hasEnumPrototype` from `_.keys` + source = source.replace(matchFunction(source, 'keys'), function(match) { + return match + .replace(/\(hasEnumPrototype[^)]+\)(?:\s*\|\|\s*)?/, '') + .replace(/\s*if *\(\s*\)[^}]+}/, ''); + }); + + // remove `hasEnumPrototype` from `iteratorTemplate` + source = source.replace(getIteratorTemplate(source), function(match) { + return match + .replace(/(?: *\/\/.*\n)* *["'] *(?:<% *)?if *\(hasEnumPrototype *(?:&&|\))[\s\S]+?<% *} *(?:%>|["']).+/g, '') + .replace(/hasEnumPrototype *\|\|/g, ''); + }); + + return source; + } + /** * Removes the `_.isArguments` fallback from `source`. * @@ -880,6 +1017,25 @@ return source.replace(getIsFunctionFallback(source), ''); } + /** + * Removes all `iteratesOwnLast` references from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeIteratesOwnLast(source) { + // remove `iteratesOwnLast` declaration and assignment + source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var iteratesOwnLast\b.*|.+?iteratesOwnLast *=.+/g, ''); + + // remove `iteratesOwnLast` from `shimIsPlainObject` + source = source.replace(matchFunction(source, 'shimIsPlainObject'), function(match) { + return match.replace(/(?:\s*\/\/.*)*\n( *)if *\(iteratesOwnLast[\s\S]+?\n\1}/, ''); + }); + + return source; + } + /** * Removes the `Object.keys` object iteration optimization from `source`. * @@ -892,53 +1048,95 @@ // remove optimized branch in `iteratorTemplate` source = source.replace(getIteratorTemplate(source), function(match) { - return match.replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(isKeysFast[\s\S]+?["']\1<% *} *else *\{ *%>.+\n([\s\S]+?) *["']\1<% *} *%>.+/, "'\\n' +\n$2"); + return match.replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(isKeysFast[\s\S]+?["']\1<% *} *else *{ *%>.+\n([\s\S]+?) *["']\1<% *} *%>.+/, "'\\n' +\n$2"); }); - // remove data object property assignment in `createIterator` - source = source.replace(matchFunction(source, 'createIterator'), function(match) { - return match.replace(/ *'isKeysFast':.+\n/, ''); + return source; + } + + /** + * Removes all `noArgsClass` references from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeNoArgsClass(source) { + source = removeVar(source, 'noArgsClass'); + + // replace `noArgsClass` in the `_.isArguments` fallback + source = source.replace(getIsArgumentsFallback(source), function(match) { + return match.replace(/noArgsClass/g, '!isArguments(arguments)'); + }); + + // remove `noArgsClass` from `_.isEmpty` + source = source.replace(matchFunction(source, 'isEmpty'), function(match) { + return match.replace(/ *\|\| *\(noArgsClass *&&[^)]+?\)\)/g, ''); }); return source; } /** - * Removes all `argsAreObjects` references from `source`. + * Removes all `noCharByIndex` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeArgsAreObjects(source) { - source = removeVar(source, 'argsAreObjects'); + function removeNoCharByIndex(source) { + source = removeVar(source, 'noCharByIndex'); - // remove `argsAreObjects` from `_.isArray` - source = source.replace(matchFunction(source, 'isArray'), function(match) { - return match.replace(/\(argsAreObjects && *([^)]+)\)/g, '$1'); + // remove `noCharByIndex` from `_.at` + source = source.replace(matchFunction(source, 'at'), function(match) { + return match.replace(/^ *if *\(noCharByIndex[^}]+}\n/m, ''); }); - // remove `argsAreObjects` from `_.isEqual` - source = source.replace(matchFunction(source, 'isEqual'), function(match) { - return match.replace(/!argsAreObjects[^:]+:\s*/g, ''); + // remove `noCharByIndex` from `_.reduceRight` + source = source.replace(matchFunction(source, 'reduceRight'), function(match) { + return match.replace(/}\s*else if *\(noCharByIndex[^}]+/, ''); + }); + + // remove `noCharByIndex` from `_.toArray` + source = source.replace(matchFunction(source, 'toArray'), function(match) { + return match.replace(/noCharByIndex[^:]+:/, ''); + }); + + // `noCharByIndex` from `iteratorTemplate` + source = source.replace(getIteratorTemplate(source), function(match) { + return match + .replace(/'if *\(<%= *arrays *%>[^']*/, '$&\\n') + .replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(noCharByIndex[\s\S]+?["']\1<% *} *%>.+/, ''); }); return source; } /** - * Removes all `noArgsClass` references from `source`. + * Removes all `nonEnumArgs` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeNoArgsClass(source) { - source = removeVar(source, 'noArgsClass'); + function removeNonEnumArgs(source) { + source = removeFromCreateIterator(source, 'nonEnumArgs'); - // remove `noArgsClass` from `_.isEmpty` - source = source.replace(matchFunction(source, 'isEmpty'), function(match) { - return match.replace(/ *\|\| *\(noArgsClass *&&[^)]+?\)\)/g, ''); + // remove `nonEnumArgs` declaration and assignment + source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var nonEnumArgs\b.*|.+?nonEnumArgs *=.+/g, ''); + + // remove `nonEnumArgs` from `_.keys` + source = source.replace(matchFunction(source, 'keys'), function(match) { + return match + .replace(/(?:\s*\|\|\s*)?\(nonEnumArgs[^)]+\)\)/, '') + .replace(/\s*if *\(\s*\)[^}]+}/, ''); + }); + + // remove `nonEnumArgs` from `iteratorTemplate` + source = source.replace(getIteratorTemplate(source), function(match) { + return match + .replace(/(?: *\/\/.*\n)*( *["'] *)<% *} *else *if *\(nonEnumArgs[\s\S]+?(\1<% *} *%>.+)/, '$2') + .replace(/ *\|\| *nonEnumArgs/, ''); }); return source; @@ -953,7 +1151,7 @@ */ function removeNoNodeClass(source) { // remove `noNodeClass` assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *\{(?:\s*\/\/.*)*\n *var noNodeClass[\s\S]+?catch[^}]+}\n/, ''); + source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *{(?:\s*\/\/.*)*\n *var noNodeClass[\s\S]+?catch[^}]+}\n/, ''); // remove `noNodeClass` from `shimIsPlainObject` source = source.replace(matchFunction(source, 'shimIsPlainObject'), function(match) { @@ -1070,7 +1268,7 @@ } /** - * Hard-codes the `useStrict` template option value for `iteratorTemplate`. + * Hard-codes the `strict` template option value for `iteratorTemplate`. * * @private * @param {String} source The source to process. @@ -1078,13 +1276,12 @@ * @returns {String} Returns the modified source. */ function setUseStrictOption(source, value) { - // inject "use strict" - if (value) { - source = source.replace(/^[\s\S]*?function[^{]+{/, "$&\n 'use strict';"); - } - // replace `useStrict` branch in `iteratorTemplate` with hard-coded option + // inject or remove the "use strict" directive + source = source.replace(/^([\s\S]*?function[^{]+{)(?:\s*'use strict';)?/, '$1' + (value ? "\n 'use strict';" : '')); + + // replace `strict` branch in `iteratorTemplate` with hard-coded option source = source.replace(getIteratorTemplate(source), function(match) { - return match.replace(/(?: *\/\/.*\n)*(\s*)["'] *<%.+?useStrict.+/, value ? '$1"\'use strict\';\\n" +' : ''); + return match.replace(/(template\()(?:\s*"'use strict.+)?/, '$1' + (value ? '\n "\'use strict\';\\n" +' : '')); }); return source; @@ -1110,7 +1307,7 @@ // used to report invalid command-line arguments var invalidArgs = _.reject(options.slice(options[0] == 'node' ? 2 : 0), function(value, index, options) { if (/^(?:-o|--output)$/.test(options[index - 1]) || - /^(?:category|exclude|exports|iife|include|moduleId|minus|plus|settings|template)=.*$/i.test(value)) { + /^(?:category|exclude|exports|iife|include|moduleId|minus|plus|settings|template)=.*$/.test(value)) { return true; } return [ @@ -1118,6 +1315,7 @@ 'csp', 'legacy', 'mobile', + 'modern', 'modularize', 'strict', 'underscore', @@ -1126,6 +1324,7 @@ '-h', '--help', '-m', '--minify', '-o', '--output', + '-p', '--source-map', '-s', '--silent', '-V', '--version' ].indexOf(value) > -1; @@ -1161,7 +1360,7 @@ /*------------------------------------------------------------------------*/ // backup `dependencyMap` to restore later - var dependencyBackup = _.clone(dependencyMap, true); + var dependencyBackup = _.cloneDeep(dependencyMap); // used to specify a custom IIFE to wrap Lo-Dash var iife = options.reduce(function(result, value) { @@ -1169,40 +1368,57 @@ return match ? match[1] : result; }, null); - // flag used to specify a Backbone build + // the path to the source file + var filePath = path.join(__dirname, 'lodash.js'); + + // flag to specify a Backbone build var isBackbone = options.indexOf('backbone') > -1; - // flag used to specify a Content Security Policy build + // flag to specify a Content Security Policy build var isCSP = options.indexOf('csp') > -1 || options.indexOf('CSP') > -1; - // flag used to specify only creating the debug build + // flag to specify only creating the debug build var isDebug = options.indexOf('-d') > -1 || options.indexOf('--debug') > -1; - // flag used to specify a legacy build - var isLegacy = options.indexOf('legacy') > -1; + // flag to indicate that a custom IIFE was specified + var isIIFE = typeof iife == 'string'; - // flag used to specify an Underscore build - var isUnderscore = options.indexOf('underscore') > -1; + // flag to specify creating a source map for the minified source + var isMapped = options.indexOf('-p') > -1 || options.indexOf('--source-map') > -1; - // flag used to specify only creating the minified build - var isMinify = !isDebug && options.indexOf('-m') > -1 || options.indexOf('--minify')> -1; + // flag to specify only creating the minified build + var isMinify = options.indexOf('-m') > -1 || options.indexOf('--minify') > -1; - // flag used to specify a mobile build - var isMobile = !isLegacy && (isCSP || isUnderscore || options.indexOf('mobile') > -1); + // flag to specify a mobile build + var isMobile = isCSP || options.indexOf('mobile') > -1; - // flag used to specify a modularize build + // flag to specify a modern build + var isModern = isMobile || options.indexOf('modern') > -1; + + // flag to specify a modularize build var isModularize = options.indexOf('modularize') > -1; - // flag used to specify writing output to standard output + // flag to specify writing output to standard output var isStdOut = options.indexOf('-c') > -1 || options.indexOf('--stdout') > -1; - // flag used to specify skipping status updates normally logged to the console + // flag to specify skipping status updates normally logged to the console var isSilent = isStdOut || options.indexOf('-s') > -1 || options.indexOf('--silent') > -1; - // flag used to specify `_.assign`, `_.bindAll`, and `_.defaults` are + // flag to specify `_.assign`, `_.bindAll`, and `_.defaults` are // constructed using the "use strict" directive var isStrict = options.indexOf('strict') > -1; + // flag to specify an Underscore build + var isUnderscore = isBackbone || options.indexOf('underscore') > -1; + + // flag to specify a legacy build + var isLegacy = !(isModern || isUnderscore) && options.indexOf('legacy') > -1; + + // used to specify methods of specific categories + var categories = options.reduce(function(result, value) { + return /category/.test(value) ? optionToArray(value) : result; + }, []); + // used to specify the ways to export the `lodash` function var exportsOptions = options.reduce(function(result, value) { return /exports/.test(value) ? optionToArray(value).sort() : result; @@ -1244,27 +1460,39 @@ 'moduleId': moduleId })); - // flag used to specify a template build + // flag to specify a template build var isTemplate = !!templatePattern; // the lodash.js source - var source = fs.readFileSync(path.join(__dirname, 'lodash.js'), 'utf8'); + var source = fs.readFileSync(filePath, 'utf8'); - // flag used to specify replacing Lo-Dash's `_.clone` with Underscore's + // flag to specify replacing Lo-Dash's `_.clone` with Underscore's var useUnderscoreClone = isUnderscore; - // flags used to specify exposing Lo-Dash methods in an Underscore build + // flags to specify exposing Lo-Dash methods in an Underscore build var exposeAssign = !isUnderscore, exposeForIn = !isUnderscore, exposeForOwn = !isUnderscore, exposeIsPlainObject = !isUnderscore; + // flags to specify export options + var isAMD = exportsOptions.indexOf('amd') > -1, + isCommonJS = exportsOptions.indexOf('commonjs') > -1, + isGlobal = exportsOptions.indexOf('global') > -1, + isNode = exportsOptions.indexOf('node') > -1; + /*------------------------------------------------------------------------*/ // names of methods to include in the build var buildMethods = !isTemplate && (function() { var result; + var includeMethods = options.reduce(function(accumulator, value) { + return /include/.test(value) + ? _.union(accumulator, optionToMethodsArray(source, value)) + : accumulator; + }, []); + var minusMethods = options.reduce(function(accumulator, value) { return /exclude|minus/.test(value) ? _.union(accumulator, optionToMethodsArray(source, value)) @@ -1277,59 +1505,66 @@ : accumulator; }, []); - // add method names explicitly - options.some(function(value) { - return /include/.test(value) && - (result = getDependencies(optionToMethodsArray(source, value))); - }); - - // include Lo-Dash's methods if explicitly requested - if (isUnderscore && result) { - exposeAssign = result.indexOf('assign') > -1; - exposeForIn = result.indexOf('forIn') > -1; - exposeForOwn = result.indexOf('forOwn') > -1; - exposeIsPlainObject = result.indexOf('isPlainObject') > -1; - useUnderscoreClone = result.indexOf('clone') < 0; + // set flags to include Lo-Dash's methods if explicitly requested + if (isUnderscore) { + var methods = _.without.apply(_, [_.union(includeMethods, plusMethods)].concat(minusMethods)); + exposeAssign = methods.indexOf('assign') > -1; + exposeForIn = methods.indexOf('forIn') > -1; + exposeForOwn = methods.indexOf('forOwn') > -1; + exposeIsPlainObject = methods.indexOf('isPlainObject') > -1; + + methods = _.without.apply(_, [plusMethods].concat(minusMethods)); + useUnderscoreClone = methods.indexOf('clone') < 0; } // update dependencies - if (isMobile) { - dependencyMap.reduceRight = ['forEach', 'keys']; + if (isModern || isUnderscore) { + dependencyMap.reduceRight = _.without(dependencyMap.reduceRight, 'isEqual', 'isString'); } if (isUnderscore) { - dependencyMap.contains = ['indexOf']; - dependencyMap.isEqual = ['isArray', 'isFunction']; + dependencyMap.contains = _.without(dependencyMap.contains, 'isString'); + dependencyMap.countBy = _.without(dependencyMap.countBy, 'isEqual', 'keys'); + dependencyMap.every = _.without(dependencyMap.every, 'isEqual', 'keys'); + dependencyMap.filter = _.without(dependencyMap.filter, 'isEqual'); + dependencyMap.find = _.without(dependencyMap.find, 'isEqual', 'keys'); + dependencyMap.groupBy = _.without(dependencyMap.groupBy, 'isEqual', 'keys'); + dependencyMap.isEqual = _.without(dependencyMap.isEqual, 'forIn', 'isArguments'); dependencyMap.isEmpty = ['isArray', 'isString']; - dependencyMap.max = ['isArray']; - dependencyMap.min = ['isArray']; - dependencyMap.pick = []; - dependencyMap.template = ['defaults', 'escape']; + dependencyMap.map = _.without(dependencyMap.map, 'isEqual', 'keys'); + dependencyMap.max = _.without(dependencyMap.max, 'isEqual', 'isString', 'keys'); + dependencyMap.min = _.without(dependencyMap.min, 'isEqual', 'isString', 'keys'); + dependencyMap.pick = _.without(dependencyMap.pick, 'forIn', 'isObject'); + dependencyMap.reduce = _.without(dependencyMap.reduce, 'isEqual', 'keys'); + dependencyMap.reject = _.without(dependencyMap.reject, 'isEqual', 'keys'); + dependencyMap.some = _.without(dependencyMap.some, 'isEqual', 'keys'); + dependencyMap.sortBy = _.without(dependencyMap.sortBy, 'isEqual', 'keys'); + dependencyMap.sortedIndex = _.without(dependencyMap.sortedIndex, 'isEqual', 'keys'); + dependencyMap.template = _.without(dependencyMap.template, 'keys', 'values'); + dependencyMap.uniq = _.without(dependencyMap.uniq, 'isEqual', 'keys'); + dependencyMap.where.push('find', 'isEmpty'); if (useUnderscoreClone) { - dependencyMap.clone = ['assign', 'isArray']; + dependencyMap.clone = _.without(dependencyMap.clone, 'forEach', 'forOwn'); } } + + // add method names explicitly + if (includeMethods.length) { + result = getDependencies(includeMethods); + } // add method names required by Backbone and Underscore builds if (isBackbone && !result) { result = getDependencies(backboneDependencies); } - if (isUnderscore && !result) { + else if (isUnderscore && !result) { result = getDependencies(underscoreMethods); } - // add method names by category - options.some(function(value) { - if (!/category/.test(value)) { - return false; - } - // resolve method names belonging to each category (case-insensitive) - var methodNames = optionToArray(value).reduce(function(accumulator, category) { - var capitalized = category[0].toUpperCase() + category.toLowerCase().slice(1); - return accumulator.concat(getMethodsByCategory(source, capitalized)); - }, []); - - return (result = _.union(result || [], getDependencies(methodNames))); - }); - + if (categories.length) { + result = _.union(result || [], getDependencies(categories.reduce(function(accumulator, category) { + // resolve method names belonging to each category (case-insensitive) + return accumulator.concat(getMethodsByCategory(source, capitalize(category))); + }, []))); + } if (!result) { result = allMethods.slice(); } @@ -1337,7 +1572,7 @@ result = _.union(result, getDependencies(plusMethods)); } if (minusMethods.length) { - result = _.without.apply(_, [result].concat(minusMethods, getDependants(result))); + result = _.without.apply(_, [result].concat(minusMethods, getDependants(minusMethods))); } return result; }()); @@ -1362,19 +1597,27 @@ source = replaceVar(source, 'noArgsClass', 'true'); source = removeKeysOptimization(source); } - if (isBackbone || isUnderscore) { - // add Underscore style chaining - source = addChainMethods(source); + if (isMobile || isUnderscore) { + source = removeKeysOptimization(source); } - if (isUnderscore) { - // remove unneeded variables - source = removeVar(source, 'cloneableClasses'); - source = removeVar(source, 'ctorByClass'); - - // remove large array optimizations - source = removeFunction(source, 'cachedContains'); - source = removeVar(source, 'largeArraySize'); + if (isModern || isUnderscore) { + source = removeHasDontEnumBug(source); + source = removeHasEnumPrototype(source); + source = removeIteratesOwnLast(source); + source = removeNoCharByIndex(source); + source = removeNoNodeClass(source); + if (!isMobile) { + source = removeNonEnumArgs(source); + } + } + if (isModern) { + // remove `_.isPlainObject` fallback + source = source.replace(matchFunction(source, 'isPlainObject'), function(match) { + return match.replace(/!getPrototypeOf.+?: */, ''); + }); + } + if (isUnderscore) { // replace `_.assign` source = replaceFunction(source, 'assign', [ ' function assign(object) {', @@ -1382,10 +1625,10 @@ ' return object;', ' }', ' for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {', - ' var iteratee = arguments[argsIndex];', - ' if (iteratee) {', - ' for (var key in iteratee) {', - ' object[key] = iteratee[key];', + ' var iterable = arguments[argsIndex];', + ' if (iterable) {', + ' for (var key in iterable) {', + ' object[key] = iterable[key];', ' }', ' }', ' }', @@ -1397,7 +1640,7 @@ if (useUnderscoreClone) { source = replaceFunction(source, 'clone', [ ' function clone(value) {', - ' return value && objectTypes[typeof value]', + ' return isObject(value)', ' ? (isArray(value) ? slice(value) : assign({}, value))', ' : value', ' }' @@ -1427,11 +1670,11 @@ ' return object;', ' }', ' for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {', - ' var iteratee = arguments[argsIndex];', - ' if (iteratee) {', - ' for (var key in iteratee) {', + ' var iterable = arguments[argsIndex];', + ' if (iterable) {', + ' for (var key in iterable) {', ' if (object[key] == null) {', - ' object[key] = iteratee[key];', + ' object[key] = iterable[key];', ' }', ' }', ' }', @@ -1502,6 +1745,107 @@ ' }' ].join('\n')); + // replace `_.isEqual` + source = replaceFunction(source, 'isEqual', [ + ' function isEqual(a, b, stackA, stackB) {', + ' if (a === b) {', + ' return a !== 0 || (1 / a == 1 / b);', + ' }', + ' var type = typeof a,', + ' otherType = typeof b;', + '', + ' if (a === a &&', + " (!a || (type != 'function' && type != 'object')) &&", + " (!b || (otherType != 'function' && otherType != 'object'))) {", + ' return false;', + ' }', + ' if (a == null || b == null) {', + ' return a === b;', + ' }', + ' var className = toString.call(a),', + ' otherClass = toString.call(b);', + '', + ' if (className != otherClass) {', + ' return false;', + ' }', + ' switch (className) {', + ' case boolClass:', + ' case dateClass:', + ' return +a == +b;', + '', + ' case numberClass:', + ' return a != +a', + ' ? b != +b', + ' : (a == 0 ? (1 / a == 1 / b) : a == +b);', + '', + ' case regexpClass:', + ' case stringClass:', + " return a == b + '';", + ' }', + ' var isArr = className == arrayClass;', + ' if (!isArr) {', + ' if (a.__wrapped__ || b.__wrapped__) {', + ' return isEqual(a.__wrapped__ || a, b.__wrapped__ || b, stackA, stackB);', + ' }', + ' if (className != objectClass) {', + ' return false;', + ' }', + ' var ctorA = a.constructor,', + ' ctorB = b.constructor;', + '', + ' if (ctorA != ctorB && !(', + ' isFunction(ctorA) && ctorA instanceof ctorA &&', + ' isFunction(ctorB) && ctorB instanceof ctorB', + ' )) {', + ' return false;', + ' }', + ' }', + ' stackA || (stackA = []);', + ' stackB || (stackB = []);', + '', + ' var length = stackA.length;', + ' while (length--) {', + ' if (stackA[length] == a) {', + ' return stackB[length] == b;', + ' }', + ' }', + ' var result = true,', + ' size = 0;', + '', + ' stackA.push(a);', + ' stackB.push(b);', + '', + ' if (isArr) {', + ' size = b.length;', + ' result = size == a.length;', + '', + ' if (result) {', + ' while (size--) {', + ' if (!(result = isEqual(a[size], b[size], stackA, stackB))) {', + ' break;', + ' }', + ' }', + ' }', + ' return result;', + ' }', + ' forIn(b, function(value, key, b) {', + ' if (hasOwnProperty.call(b, key)) {', + ' size++;', + ' return !(result = hasOwnProperty.call(a, key) && isEqual(a[key], value, stackA, stackB)) && indicatorObject;', + ' }', + ' });', + '', + ' if (result) {', + ' forIn(a, function(value, key, a) {', + ' if (hasOwnProperty.call(a, key)) {', + ' return !(result = --size > -1) && indicatorObject;', + ' }', + ' });', + ' }', + ' return result;', + ' }' + ].join('\n')); + // replace `_.omit` source = replaceFunction(source, 'omit', [ ' function omit(object) {', @@ -1535,6 +1879,14 @@ ' }' ].join('\n')); + // replace `_.result` + source = replaceFunction(source, 'result', [ + ' function result(object, property) {', + ' var value = object ? object[property] : null;', + ' return isFunction(value) ? object[property]() : value;', + ' }' + ].join('\n')); + // replace `_.template` source = replaceFunction(source, 'template', [ ' function template(text, data, options) {', @@ -1553,12 +1905,17 @@ '', ' text.replace(reDelimiters, function(match, escapeValue, interpolateValue, evaluateValue, offset) {', ' source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar);', - ' source +=', - ' escapeValue ? "\' +\\n_.escape(" + escapeValue + ") +\\n\'" :', - ' evaluateValue ? "\';\\n" + evaluateValue + ";\\n__p += \'" :', - ' interpolateValue ? "\' +\\n((__t = (" + interpolateValue + ")) == null ? \'\' : __t) +\\n\'" : \'\';', - '', + ' if (escapeValue) {', + ' source += "\' +\\n_.escape(" + escapeValue + ") +\\n\'";', + ' }', + ' if (evaluateValue) {', + ' source += "\';\\n" + evaluateValue + ";\\n__p += \'";', + ' }', + ' if (interpolateValue) {', + ' source += "\' +\\n((__t = (" + interpolateValue + ")) == null ? \'\' : __t) +\\n\'";', + ' }', ' index = offset + match.length;', + ' return match;', ' });', '', ' source += "\';\\n";', @@ -1629,6 +1986,15 @@ ' }' ].join('\n')); + // replace `_.where` + source = replaceFunction(source, 'where', [ + ' function where(collection, properties, first) {', + ' return (first && isEmpty(properties))', + ' ? null', + ' : (first ? find : filter)(collection, properties);', + ' }' + ].join('\n')); + // replace `_.without` source = replaceFunction(source, 'without', [ ' function without(array) {', @@ -1646,11 +2012,34 @@ ' }' ].join('\n')); - // remove `arguments` object and `argsAreObjects` check from `_.isEqual` - source = source.replace(matchFunction(source, 'isEqual'), function(match) { - return match - .replace(/^ *if *\(.+== argsClass[^}]+}\n/gm, '') - .replace(/!argsAreObjects[^:]+:\s*/g, ''); + // add `_.findWhere` + source = source.replace(matchFunction(source, 'find'), function (match) { + return match + [ + '', + ' function findWhere(object, properties) {', + ' return where(object, properties, true);', + ' }', + '' + ].join('\n') + }); + + source = source.replace(getMethodAssignments(source), function(match) { + return match.replace(/^( *)lodash.find *=.+/m, '$&\n$1lodash.findWhere = findWhere;'); + }); + + // add Underscore style chaining + source = addChainMethods(source); + + // remove `_.templateSettings.imports assignment + source = source.replace(/,[^']*'imports':[^}]+}/, ''); + + // remove large array optimizations + source = removeFunction(source, 'cachedContains'); + source = removeVar(source, 'largeArraySize'); + + // remove `_.isEqual` use from `createCallback` + source = source.replace(matchFunction(source, 'createCallback'), function(match) { + return match.replace(/isEqual\(([^,]+), *([^,]+)[^)]+\)/, '$1 === $2'); }); // remove conditional `charCodeCallback` use from `_.max` and `_.min` @@ -1660,32 +2049,22 @@ }); }); + // remove unneeded variables + if (useUnderscoreClone) { + source = removeVar(source, 'cloneableClasses'); + source = removeVar(source, 'ctorByClass'); + } // remove unused features from `createBound` - if (buildMethods.indexOf('partial') == -1) { + if (buildMethods.indexOf('partial') < 0 && buildMethods.indexOf('partialRight') < 0) { source = source.replace(matchFunction(source, 'createBound'), function(match) { return match + .replace(/, *right[^)]*/, '') .replace(/(function createBound\([^{]+{)[\s\S]+?(\n *function bound)/, '$1$2') - .replace(/thisBinding *=[^}]+}/, 'thisBinding = thisArg;\n'); + .replace(/thisBinding *=[^}]+}/, 'thisBinding = thisArg;\n') + .replace(/\(args *=.+/, 'partialArgs.concat(slice(args))'); }); } } - if (isMobile) { - source = replaceVar(source, 'isKeysFast', 'false'); - source = removeKeysOptimization(source); - source = removeNoNodeClass(source); - - // remove `prototype` [[Enumerable]] fix from `_.keys` - source = source.replace(matchFunction(source, 'keys'), function(match) { - return match.replace(/(?:\s*\/\/.*)*(\s*return *).+?propertyIsEnumerable[\s\S]+?: */, '$1'); - }); - - // remove `prototype` [[Enumerable]] fix from `iteratorTemplate` - source = source.replace(getIteratorTemplate(source), function(match) { - return match - .replace(/(?: *\/\/.*\n)* *["'] *(?:<% *)?if *\(!hasDontEnumBug *(?:&&|\))[\s\S]+?<% *} *(?:%>|["']).+/g, '') - .replace(/!hasDontEnumBug *\|\|/g, ''); - }); - } vm.runInContext(source, context); return context._; }()); @@ -1696,15 +2075,6 @@ source = buildTemplate(templatePattern, templateSettings); } else { - // simplify template snippets by removing unnecessary brackets - source = source.replace( - RegExp("{(\\\\n' *\\+\\s*.*?\\+\\n\\s*' *)}(?:\\\\n)?' *([,\\n])", 'g'), "$1'$2" - ); - - source = source.replace( - RegExp("{(\\\\n' *\\+\\s*.*?\\+\\n\\s*' *)}(?:\\\\n)?' *\\+", 'g'), "$1;\\n'+" - ); - // remove methods from the build allMethods.forEach(function(otherName) { if (!_.contains(buildMethods, otherName)) { @@ -1718,6 +2088,13 @@ source = removeIsArgumentsFallback(source); } + // remove `iteratorTemplate` dependency checks from `_.template` + source = source.replace(matchFunction(source, 'template'), function(match) { + return match + .replace(/iteratorTemplate *&& */g, '') + .replace(/iteratorTemplate *\? *([^:]+?) *:[^,;]+/g, '$1'); + }); + /*----------------------------------------------------------------------*/ if (isLegacy) { @@ -1753,13 +2130,19 @@ source = removeIsArgumentsFallback(source); } - source = removeFromCreateIterator(source, 'nativeKeys'); - source = removeCreateFunction(source); } + if (isModern) { + source = removeArgsAreObjects(source); + source = removeHasObjectSpliceBug(source); + source = removeIsArgumentsFallback(source); + } + if (isModern || isUnderscore) { + source = removeNoArgsClass(source); + source = removeNoNodeClass(source); + } + if (isMobile || isUnderscore) { + source = removeVar(source, 'iteratorTemplate'); - /*----------------------------------------------------------------------*/ - - if (isMobile) { // inline all functions defined with `createIterator` _.functions(lodash).forEach(function(methodName) { // strip leading underscores to match pseudo private functions @@ -1771,75 +2154,27 @@ }); } }); + } + if (isUnderscore) { + // remove `_.assign`, `_.forIn`, `_.forOwn`, and `_.isPlainObject` assignments + (function() { + var snippet = getMethodAssignments(source), + modified = snippet; - if (isUnderscore) { - // remove `_.assign`, `_.forIn`, `_.forOwn`, and `_.isPlainObject` assignments - (function() { - var snippet = getMethodAssignments(source), - modified = snippet; - - if (!exposeAssign) { - modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.assign *= *.+\n/m, ''); - } - if (!exposeForIn) { - modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.forIn *= *.+\n/m, ''); - } - if (!exposeForOwn) { - modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.forOwn *= *.+\n/m, ''); - } - if (!exposeIsPlainObject) { - modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.isPlainObject *= *.+\n/m, ''); - } - source = source.replace(snippet, modified); - }()); - - // replace `isArguments` and its fallback - (function() { - var snippet = matchFunction(source, 'isArguments').trimRight(); - snippet = snippet.replace(/function isArguments/, 'lodash.isArguments = function'); - - source = removeFunction(source, 'isArguments'); - source = source.replace(getIsArgumentsFallback(source), function(match) { - return snippet + '\n' + match - .replace(/isArguments/g, 'lodash.$&') - .replace(/noArgsClass/g, '!lodash.isArguments(arguments)'); - }); - }()); - - // remove chainability from `each` and `_.forEach` - _.each(['each', 'forEach'], function(methodName) { - source = source.replace(matchFunction(source, methodName), function(match) { - return match.replace(/\n *return .+?([};\s]+)$/, '$1'); - }); - }); - - // unexpose "exit early" feature of `each`, `_.forEach`, `_.forIn`, and `_.forOwn` - _.each(['each', 'forEach', 'forIn', 'forOwn'], function(methodName) { - source = source.replace(matchFunction(source, methodName), function(match) { - return match.replace(/=== *false\)/g, '=== indicatorObject)'); - }); - }); - - // modify `_.every`, `_.find`, `_.isEqual`, and `_.some` to use the private `indicatorObject` - _.each(['every', 'isEqual'], function(methodName) { - source = source.replace(matchFunction(source, methodName), function(match) { - return match.replace(/\(result *= *(.+?)\);/g, '!(result = $1) && indicatorObject;'); - }); - }); - - source = source.replace(matchFunction(source, 'find'), function(match) { - return match.replace(/return false/, 'return indicatorObject'); - }); - - source = source.replace(matchFunction(source, 'some'), function(match) { - return match.replace(/!\(result *= *(.+?)\);/, '(result = $1) && indicatorObject;'); - }); - } - else { - source = removeArgsAreObjects(source); - source = removeHasObjectSpliceBug(source); - source = removeIsArgumentsFallback(source); - } + if (!exposeAssign) { + modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.assign *= *.+\n/m, ''); + } + if (!exposeForIn) { + modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.forIn *= *.+\n/m, ''); + } + if (!exposeForOwn) { + modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.forOwn *= *.+\n/m, ''); + } + if (!exposeIsPlainObject) { + modified = modified.replace(/^(?: *\/\/.*\s*)* *lodash\.isPlainObject *= *.+\n/m, ''); + } + source = source.replace(snippet, modified); + }()); // remove `thisArg` from unexposed `forIn` and `forOwn` _.each([ @@ -1855,32 +2190,36 @@ } }); - // remove `hasDontEnumBug`, `iteratesOwnLast`, and `nonEnumArgs` declarations and assignments - source = source - .replace(/^ *\(function\(\) *{[\s\S]+?}\(1\)\);\n/m, '') - .replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var (?:hasDontEnumBug|iteratesOwnLast|nonEnumArgs).+\n/g, ''); + // remove chainability from `each` and `_.forEach` + _.each(['each', 'forEach'], function(methodName) { + source = source.replace(matchFunction(source, methodName), function(match) { + return match.replace(/\n *return .+?([};\s]+)$/, '$1'); + }); + }); - // remove `iteratesOwnLast` from `shimIsPlainObject` - source = source.replace(matchFunction(source, 'shimIsPlainObject'), function(match) { - return match.replace(/(?:\s*\/\/.*)*\n( *)if *\(iteratesOwnLast[\s\S]+?\n\1}/, ''); + // unexpose "exit early" feature of `each`, `_.forEach`, `_.forIn`, and `_.forOwn` + _.each(['each', 'forEach', 'forIn', 'forOwn'], function(methodName) { + source = source.replace(matchFunction(source, methodName), function(match) { + return match.replace(/=== *false\)/g, '=== indicatorObject)'); + }); }); - // remove `noCharByIndex` from `_.reduceRight` - source = source.replace(matchFunction(source, 'reduceRight'), function(match) { - return match.replace(/}\s*else if *\(noCharByIndex[^}]+/, ''); + // modify `_.every`, `_.find`, `_.isEqual`, and `_.some` to use the private `indicatorObject` + _.each(['every', 'isEqual'], function(methodName) { + source = source.replace(matchFunction(source, methodName), function(match) { + return match.replace(/\(result *= *(.+?)\);/g, '!(result = $1) && indicatorObject;'); + }); }); - // remove `noCharByIndex` from `_.toArray` - source = source.replace(matchFunction(source, 'toArray'), function(match) { - return match.replace(/noCharByIndex[^:]+:/, ''); + source = source.replace(matchFunction(source, 'find'), function(match) { + return match.replace(/return false/, 'return indicatorObject'); }); - source = removeVar(source, 'iteratorTemplate'); - source = removeCreateFunction(source); - source = removeNoArgsClass(source); - source = removeNoNodeClass(source); + source = source.replace(matchFunction(source, 'some'), function(match) { + return match.replace(/!\(result *= *(.+?)\);/, '(result = $1) && indicatorObject;'); + }); } - else { + if (!(isMobile || isUnderscore)) { // inline `iteratorTemplate` template source = source.replace(getIteratorTemplate(source), function() { var snippet = getFunctionSource(lodash._iteratorTemplate); @@ -1898,7 +2237,7 @@ .replace(/'(?:\\n|\s)+'/g, "''") .replace(/__p *\+= *' *';/g, '') .replace(/(__p *\+= *)' *' *\+/g, '$1') - .replace(/(\{) *;|; *(\})/g, '$1$2') + .replace(/({) *;|; *(})/g, '$1$2') .replace(/\(\(__t *= *\( *([^)]+) *\)\) *== *null *\? *'' *: *__t\)/g, '($1)'); // remove the with-statement @@ -1906,7 +2245,7 @@ // minor cleanup snippet = snippet - .replace(/obj *\|\| *\(obj *= *\{}\);/, '') + .replace(/obj *\|\| *\(obj *= *{}\);/, '') .replace(/var __p = '';\s*__p \+=/, 'var __p ='); // remove comments, including sourceURLs @@ -1921,11 +2260,11 @@ // customize Lo-Dash's IIFE (function() { - if (typeof iife == 'string') { + if (isIIFE) { var token = '%output%', index = iife.indexOf(token); - source = source.match(/\/\*![\s\S]+?\*\/\n/) + + source = source.match(/^\/\**[\s\S]+?\*\/\n/) + iife.slice(0, index) + source.replace(/^[\s\S]+?\(function[^{]+?{|}\(this\)\)[;\s]*$/g, '') + iife.slice(index + token.length); @@ -1936,11 +2275,6 @@ // customize Lo-Dash's export bootstrap (function() { - var isAMD = exportsOptions.indexOf('amd') > -1, - isCommonJS = exportsOptions.indexOf('commonjs') > -1, - isGlobal = exportsOptions.indexOf('global') > -1, - isNode = exportsOptions.indexOf('node') > -1; - if (!isAMD) { source = source.replace(/(?: *\/\/.*\n)*( *)if *\(typeof +define[\s\S]+?else /, '$1'); } @@ -1959,7 +2293,6 @@ } else { source = source.replace(/(?: *\/\/.*\n)* *(?:else )?if *\(freeExports\) *{\s*}(?:\s*else *{([\s\S]+?) *})?\n/, '$1\n'); } - if ((source.match(/\bfreeExports\b/g) || []).length < 2) { source = removeVar(source, 'freeExports'); } @@ -1967,10 +2300,7 @@ /*------------------------------------------------------------------------*/ - if (isTemplate) { - debugSource = source; - } - else { + if (!isTemplate) { // modify/remove references to removed methods/variables if (isRemoved(source, 'invert')) { source = replaceVar(source, 'htmlUnescapes', "{'&':'&','<':'<','>':'>','"':'\"',''':\"'\"}"); @@ -1998,12 +2328,7 @@ .replace(/(?:\s*\/\/.*)*\s*lodash\.prototype.+\n/g, '') .replace(/(?:\s*\/\/.*)*\s*mixin\(lodash\).+\n/, ''); } - - // assign debug source before further modifications that rely on the minifier - // to remove unused variables and other dead code - debugSource = cleanupSource(source); - - // remove associated functions, variables, and code snippets that the minifier may miss + // remove functions, variables, and snippets that the minifier may miss if (isRemoved(source, 'clone')) { source = removeVar(source, 'cloneableClasses'); source = removeVar(source, 'ctorByClass'); @@ -2013,7 +2338,7 @@ } if (isRemoved(source, 'isPlainObject')) { source = removeVar(source, 'getPrototypeOf'); - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var iteratesOwnLast;|.+?iteratesOwnLast *=.+/g, ''); + source = removeIteratesOwnLast(source); } if (isRemoved(source, 'keys')) { source = removeFunction(source, 'shimKeys'); @@ -2030,34 +2355,43 @@ } if ((source.match(/\bcreateIterator\b/g) || []).length < 2) { source = removeFunction(source, 'createIterator'); - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasDontEnumBug;|.+?hasDontEnumBug *=.+/g, ''); - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var nonEnumArgs;|.+?nonEnumArgs *=.+/g, ''); + source = removeVar(source, 'defaultsIteratorOptions'); + source = removeVar(source, 'eachIteratorOptions'); + source = removeVar(source, 'forOwnIteratorOptions'); + source = removeVar(source, 'templateIterator'); + source = removeHasDontEnumBug(source); + source = removeHasEnumPrototype(source); } - if (isRemoved(source, 'createIterator', 'bind', 'keys', 'template')) { + if (isRemoved(source, 'createIterator', 'bind', 'keys')) { source = removeVar(source, 'isBindFast'); + source = removeVar(source, 'isV8'); source = removeVar(source, 'nativeBind'); } - if (isRemoved(source, 'createIterator', 'bind', 'isArray', 'isPlainObject', 'keys', 'template')) { - source = removeVar(source, 'reNative'); - } if (isRemoved(source, 'createIterator', 'keys')) { source = removeVar(source, 'nativeKeys'); source = removeKeysOptimization(source); + source = removeNonEnumArgs(source); } - if (!source.match(/var (?:hasDontEnumBug|iteratesOwnLast|nonEnumArgs)\b/g)) { - // remove `hasDontEnumBug`, `iteratesOwnLast`, and `nonEnumArgs` assignments - source = source.replace(/ *\(function\(\) *{[\s\S]+?}\(1\)\);\n/, ''); + if (!source.match(/var (?:hasDontEnumBug|hasEnumPrototype|iteratesOwnLast|nonEnumArgs)\b/g)) { + // remove IIFE used to assign `hasDontEnumBug`, `hasEnumPrototype`, `iteratesOwnLast`, and `nonEnumArgs` + source = source.replace(/^ *\(function\(\) *{[\s\S]+?}\(1\)\);\n/m, ''); } } + debugSource = cleanupSource(source); source = cleanupSource(source); /*------------------------------------------------------------------------*/ - // used to specify creating a custom build - var isCustom = isBackbone || isLegacy || isMobile || isStrict || isUnderscore || + // flag to track if `outputPath` has been used by `callback` + var outputUsed = false; + + // flag to specify creating a custom build + var isCustom = ( + isLegacy || isMapped || isModern || isStrict || isUnderscore || outputPath || /(?:category|exclude|exports|iife|include|minus|plus)=/.test(options) || - !_.isEqual(exportsOptions, exportsAll); + !_.isEqual(exportsOptions, exportsAll) + ); // used as the basename of the output path var basename = outputPath @@ -2074,32 +2408,41 @@ } if (isDebug && isStdOut) { stdout.write(debugSource); - callback(debugSource); - } else if (!isStdOut) { - callback(debugSource, (isDebug && outputPath) || path.join(cwd, basename + '.js')); + callback({ + 'source': debugSource + }); + } + else if (!isStdOut) { + outputUsed = true; + callback({ + 'source': debugSource, + 'outputPath': outputPath || path.join(cwd, basename + '.js') + }); } } // begin the minification process if (!isDebug) { - outputPath || (outputPath = path.join(cwd, basename + '.min.js')); - + if (outputPath && outputUsed) { + outputPath = path.join(path.dirname(outputPath), path.basename(outputPath, '.js') + '.min.js'); + } else if (!outputPath) { + outputPath = path.join(cwd, basename + '.min.js'); + } minify(source, { + 'filePath': filePath, + 'isMapped': isMapped, 'isSilent': isSilent, 'isTemplate': isTemplate, + 'modes': isIIFE && ['simple', 'hybrid'], 'outputPath': outputPath, - 'onComplete': function(source) { - // inject "use strict" directive - if (isStrict) { - source = source.replace(/^([\s\S]*?function[^{]+{)([^"'])/, '$1"use strict";$2'); - } + 'onComplete': function(data) { if (isCustom) { - source = addCommandsToHeader(source, options); + data.source = addCommandsToHeader(data.source, options); } if (isStdOut) { - stdout.write(source); - callback(source); + stdout.write(data.source); + callback(data); } else { - callback(source, outputPath); + callback(data); } } }); @@ -2114,8 +2457,16 @@ } else { // or invoked directly - build(process.argv, function(source, filePath) { - filePath && fs.writeFileSync(filePath, source, 'utf8'); + build(process.argv, function(data) { + var outputPath = data.outputPath, + sourceMap = data.sourceMap; + + if (outputPath) { + fs.writeFileSync(outputPath, data.source, 'utf8'); + if (sourceMap) { + fs.writeFileSync(path.join(path.dirname(outputPath), path.basename(outputPath, '.js') + '.map'), sourceMap, 'utf8'); + } + } }); } }()); diff --git a/build/minify.js b/build/minify.js index 8bc9f7a1c8..becc7fca6e 100755 --- a/build/minify.js +++ b/build/minify.js @@ -2,26 +2,56 @@ ;(function() { 'use strict'; - /** The Node filesystem, path, `zlib`, and child process modules */ + /** Load Node modules */ var fs = require('fs'), - gzip = require('zlib').gzip, + https = require('https'), path = require('path'), - spawn = require('child_process').spawn; + spawn = require('child_process').spawn, + tar = require('../vendor/tar/tar.js'), + zlib = require('zlib'); + + /** Load other modules */ + var preprocess = require('./pre-compile.js'), + postprocess = require('./post-compile.js'); + + /** The Git object ID of `closure-compiler.tar.gz` */ + var closureId = '23cf67d0f0b979d97631fc108a2a43bb82225994'; + + /** The Git object ID of `uglifyjs.tar.gz` */ + var uglifyId = '326ede8f4a3d8e0ae82cec7b9579d45892836629'; /** The path of the directory that is the base of the repository */ var basePath = fs.realpathSync(path.join(__dirname, '..')); - /** The path of the directory where the Closure Compiler is located */ - var closurePath = path.join(basePath, 'vendor', 'closure-compiler', 'compiler.jar'); + /** The path of the `vendor` directory */ + var vendorPath = path.join(basePath, 'vendor'); - /** Load other modules */ - var preprocess = require('./pre-compile.js'), - postprocess = require('./post-compile.js'), - uglifyJS = require('../vendor/uglifyjs/uglify-js.js'); + /** The path to the Closure Compiler `.jar` */ + var closurePath = path.join(vendorPath, 'closure-compiler', 'compiler.jar'); + + /** The path to the UglifyJS module */ + var uglifyPath = path.join(vendorPath, 'uglifyjs', 'tools', 'node.js'); /** The Closure Compiler command-line options */ var closureOptions = ['--warning_level=QUIET']; + /** The media type for raw blob data */ + var mediaType = 'application/vnd.github.v3.raw'; + + /** Used to reference parts of the blob href */ + var location = (function() { + var host = 'api.github.com', + origin = 'https://api.github.com', + pathname = '/repos/bestiejs/lodash/git/blobs'; + + return { + 'host': host, + 'href': origin + pathname, + 'origin': origin, + 'pathname': pathname + }; + }()); + /** The Closure Compiler optimization modes */ var optimizationModes = { 'simple': 'SIMPLE_OPTIMIZATIONS', @@ -50,6 +80,7 @@ * onComplete - The function called once minification has finished. */ function minify(source, options) { + var modes = ['simple', 'advanced', 'hybrid']; source || (source = ''); options || (options = {}); @@ -59,10 +90,16 @@ options = source; var filePath = options[options.length - 1], + isMapped = options.indexOf('-p') > -1 || options.indexOf('--source-map') > -1, isSilent = options.indexOf('-s') > -1 || options.indexOf('--silent') > -1, isTemplate = options.indexOf('-t') > -1 || options.indexOf('--template') > -1, outputPath = path.join(path.dirname(filePath), path.basename(filePath, '.js') + '.min.js'); + modes = options.reduce(function(result, value) { + var match = value.match(/modes=(.*)$/); + return match ? match[1].split(/, */) : result; + }, modes); + outputPath = options.reduce(function(result, value, index) { if (/-o|--output/.test(value)) { result = options[index + 1]; @@ -72,14 +109,54 @@ }, outputPath); options = { + 'filePath': filePath, + 'isMapped': isMapped, 'isSilent': isSilent, 'isTemplate': isTemplate, + 'modes': modes, 'outputPath': outputPath }; source = fs.readFileSync(filePath, 'utf8'); } - new Minify(source, options); + + modes = options.modes || modes; + if (options.isMapped) { + modes = modes.filter(function(mode) { + return mode != 'hybrid'; + }); + } + if (options.isTemplate) { + modes = modes.filter(function(mode) { + return mode != 'advanced'; + }); + } + options.modes = modes; + + // fetch the Closure Compiler + getDependency({ + 'id': 'closure-compiler', + 'hashId': closureId, + 'path': vendorPath, + 'title': 'the Closure Compiler', + 'onComplete': function(exception) { + var error = exception; + + // fetch UglifyJS + getDependency({ + 'id': 'uglifyjs', + 'hashId': uglifyId, + 'title': 'UglifyJS', + 'path': vendorPath, + 'onComplete': function(exception) { + error || (error = exception); + if (!error) { + new Minify(source, options); + } + } + }); + } + }); } /** @@ -104,19 +181,113 @@ this.hybrid = { 'simple': {}, 'advanced': {} }; this.uglified = {}; + this.filePath = options.filePath; + this.isMapped = !!options.isMapped; this.isSilent = !!options.isSilent; this.isTemplate = !!options.isTemplate; this.outputPath = options.outputPath; - source = preprocess(source, options); - this.source = source; + var modes = this.modes = options.modes; + source = this.source = preprocess(source, options); + + this.onComplete = options.onComplete || function(data) { + var outputPath = this.outputPath, + sourceMap = data.sourceMap; - this.onComplete = options.onComplete || function(source) { - fs.writeFileSync(this.outputPath, source, 'utf8'); + fs.writeFileSync(outputPath, data.source, 'utf8'); + if (sourceMap) { + fs.writeFileSync(getMapPath(outputPath), sourceMap, 'utf8'); + } }; // begin the minification process - closureCompile.call(this, source, 'simple', onClosureSimpleCompile.bind(this)); + if (modes.indexOf('simple') > -1) { + closureCompile.call(this, source, 'simple', onClosureSimpleCompile.bind(this)); + } else if (modes.indexOf('advanced') > -1) { + onClosureSimpleGzip.call(this); + } else { + onClosureAdvancedGzip.call(this); + } + } + + /*--------------------------------------------------------------------------*/ + + /** + * Fetches a required `.tar.gz` dependency with the given Git object ID from + * the Lo-Dash repo on GitHub. The object ID may be obtained by running + * `git hash-object path/to/dependency.tar.gz`. + * + * @private + * @param {Object} options The options object. + * id - The Git object ID of the `.tar.gz` file. + * onComplete - The function called once the extraction has finished. + * path - The path of the extraction directory. + * title - The dependency's title used in status updates logged to the console. + */ + function getDependency(options) { + options || (options = {}); + + var ran, + destPath = options.path, + hashId = options.hashId, + id = options.id, + onComplete = options.onComplete, + title = options.title; + + // exit early if dependency exists + if (fs.existsSync(path.join(destPath, id))) { + onComplete(); + return; + } + var callback = function(exception) { + if (ran) { + return; + } + if (exception) { + console.error([ + 'There was a problem installing ' + title + '.', + 'Try running the command as root, via `sudo`, or manually install by running:', + '', + "curl -H 'Accept: " + mediaType + "' " + location.href + '/' + hashId + " | tar xvz -C '" + destPath + "'", + '' + ].join('\n')); + } + ran = true; + process.removeListener('uncaughtException', callback); + onComplete(exception); + }; + + console.log('Downloading ' + title + '...'); + process.on('uncaughtException', callback); + + https.get({ + 'host': location.host, + 'path': location.pathname + '/' + hashId, + 'headers': { + // By default, all GitHub blob API endpoints return a JSON document + // containing Base64-encoded blob data. Overriding the `Accept` header + // with the GitHub raw media type returns the blob data directly. + // See http://developer.github.com/v3/media/. + 'Accept': mediaType + } + }, function(response) { + var decompressor = zlib.createUnzip(), + parser = new tar.Extract({ 'path': destPath }); + + parser.on('end', callback); + response.pipe(decompressor).pipe(parser); + }); + } + + /** + * Resolves the source map path from the given output path. + * + * @private + * @param {String} outputPath The output path. + * @returns {String} Returns the source map path. + */ + function getMapPath(outputPath) { + return path.join(path.dirname(outputPath), path.basename(outputPath, '.js') + '.map'); } /*--------------------------------------------------------------------------*/ @@ -131,36 +302,82 @@ * @param {Function} callback The function called once the process has completed. */ function closureCompile(source, mode, callback) { - // use simple optimizations when minifying template files - var options = closureOptions.slice(); - options.push('--compilation_level=' + optimizationModes[this.isTemplate ? 'simple' : mode]); + var filePath = this.filePath, + isAdvanced = mode == 'advanced', + isMapped = this.isMapped, + mapPath = getMapPath(outputPath), + options = closureOptions.slice(), + outputPath = this.outputPath; + + // remove copyright header to make other modifications easier + var license = (/^(?:\s*\/\/.*\s*|\s*\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/\s*)*/.exec(source) || [''])[0]; + if (license) { + source = source.replace(license, ''); + } - // the standard error stream, standard output stream, and the Closure Compiler process - var error = '', - output = '', - compiler = spawn('java', ['-jar', closurePath].concat(options)); + var hasIIFE = /^;?\(function[^{]+{\s*/.test(source), + isStrict = hasIIFE && /^;?\(function[^{]+{\s*["']use strict["']/.test(source); + + // to avoid stripping the IIFE, convert it to a function call + if (hasIIFE && isAdvanced) { + source = source + .replace(/\(function/, '__iife__$&') + .replace(/\(this\)\)([\s;]*(\n\/\/.+)?)$/, ', this)$1'); + } + options.push('--compilation_level=' + optimizationModes[mode]); + if (isMapped) { + options.push('--create_source_map=' + mapPath, '--source_map_format=V3'); + } + + var compiler = spawn('java', ['-jar', closurePath].concat(options)); if (!this.isSilent) { - console.log('Compressing ' + path.basename(this.outputPath, '.js') + ' using the Closure Compiler (' + mode + ')...'); + console.log('Compressing ' + path.basename(outputPath, '.js') + ' using the Closure Compiler (' + mode + ')...'); } - compiler.stdout.on('data', function(data) { - // append the data to the output stream - output += data; - }); + var error = ''; compiler.stderr.on('data', function(data) { - // append the error message to the error stream error += data; }); + var output = ''; + compiler.stdout.on('data', function(data) { + output += data; + }); + compiler.on('exit', function(status) { // `status` contains the process exit code - var exception = null; if (status) { - exception = new Error(error); + var exception = new Error(error); exception.status = status; } - callback(exception, output); + // restore IIFE and move exposed vars inside the IIFE + if (hasIIFE && isAdvanced) { + output = output + .replace(/__iife__\(/, '(') + .replace(/,\s*this\)([\s;]*(\n\/\/.+)?)$/, '(this))$1') + .replace(/^((?:var (?:\w+=(?:!0|!1|null)[,;])+)?)([\s\S]*?function[^{]+{)/, '$2$1'); + } + // inject "use strict" directive + if (isStrict) { + output = output.replace(/^[\s\S]*?function[^{]+{/, '$&"use strict";'); + } + // restore copyright header + if (license) { + output = license + output; + } + if (isMapped) { + var mapOutput = fs.readFileSync(mapPath, 'utf8'); + fs.unlinkSync(mapPath); + + output = output + .replace(/[\s;]*$/, '\n/*\n//@ sourceMappingURL=' + path.basename(mapPath)) + '\n*/'; + + mapOutput = mapOutput + .replace(/("file":)""/, '$1"' + path.basename(outputPath) + '"') + .replace(/("sources":)\["stdin"\]/, '$1["' + path.basename(filePath) + '"]'); + } + callback(exception, output, mapOutput); }); // proxy the standard input to the Closure Compiler @@ -178,31 +395,46 @@ * @param {Function} callback The function called once the process has completed. */ function uglify(source, label, callback) { - var exception, - result, - ugly = uglifyJS.uglify; - if (!this.isSilent) { console.log('Compressing ' + path.basename(this.outputPath, '.js') + ' using ' + label + '...'); } try { - result = ugly.gen_code( - // enable unsafe transformations - ugly.ast_squeeze_more( - ugly.ast_squeeze( - // munge variable and function names, excluding the special `define` - // function exposed by AMD loaders - ugly.ast_mangle(uglifyJS.parser.parse(source), { - 'except': ['define'] - } - ))), { - 'ascii_only': true + var uglifyJS = require(uglifyPath); + + // 1. parse + var toplevel = uglifyJS.parse(source); + + // 2. compress + // enable unsafe comparisons + toplevel.figure_out_scope(); + toplevel = toplevel.transform(uglifyJS.Compressor({ + 'comparisons': false, + 'unsafe_comps': true, + 'warnings': false + })); + + // 3. mangle + // excluding the `define` function exposed by AMD loaders + toplevel.figure_out_scope(); + toplevel.compute_char_frequency(); + toplevel.mangle_names({ + 'except': ['define'] + }); + + // 4. output + // restrict lines to 500 characters for consistency with the Closure Compiler + var stream = uglifyJS.OutputStream({ + 'ascii_only': true, + 'comments': /@cc_on|@license|@preserve/i, + 'max_line_len': 500, }); - } catch(e) { - exception = e; + + toplevel.print(stream); + } + catch(e) { + var exception = e; } - // lines are restricted to 500 characters for consistency with the Closure Compiler - callback(exception, result && ugly.split_lines(result, 500)); + callback(exception, stream && String(stream)); } /*--------------------------------------------------------------------------*/ @@ -213,14 +445,18 @@ * @private * @param {Object|Undefined} exception The error object. * @param {String} result The resulting minified source. + * @param {String} map The source map output. */ - function onClosureSimpleCompile(exception, result) { + function onClosureSimpleCompile(exception, result, map) { if (exception) { throw exception; } result = postprocess(result); - this.compiled.simple.source = result; - gzip(result, onClosureSimpleGzip.bind(this)); + + var simple = this.compiled.simple; + simple.source = result; + simple.sourceMap = map; + zlib.gzip(result, onClosureSimpleGzip.bind(this)); } /** @@ -234,13 +470,18 @@ if (exception) { throw exception; } - if (!this.isSilent) { - console.log('Done. Size: %d bytes.', result.length); + if (result != null) { + if (!this.isSilent) { + console.log('Done. Size: %d bytes.', result.length); + } + this.compiled.simple.gzip = result; + } + // compile the source using advanced optimizations + if (this.modes.indexOf('advanced') > -1) { + closureCompile.call(this, this.source, 'advanced', onClosureAdvancedCompile.bind(this)); + } else { + onClosureAdvancedGzip.call(this); } - this.compiled.simple.gzip = result; - - // next, compile the source using advanced optimizations - closureCompile.call(this, this.source, 'advanced', onClosureAdvancedCompile.bind(this)); } /** @@ -249,14 +490,18 @@ * @private * @param {Object|Undefined} exception The error object. * @param {String} result The resulting minified source. + * @param {String} map The source map output. */ - function onClosureAdvancedCompile(exception, result) { + function onClosureAdvancedCompile(exception, result, map) { if (exception) { throw exception; } result = postprocess(result); - this.compiled.advanced.source = result; - gzip(result, onClosureAdvancedGzip.bind(this)); + + var advanced = this.compiled.advanced; + advanced.source = result; + advanced.sourceMap = map; + zlib.gzip(result, onClosureAdvancedGzip.bind(this)); } /** @@ -270,13 +515,18 @@ if (exception) { throw exception; } - if (!this.isSilent) { - console.log('Done. Size: %d bytes.', result.length); + if (result != null) { + if (!this.isSilent) { + console.log('Done. Size: %d bytes.', result.length); + } + this.compiled.advanced.gzip = result; + } + // minify the source using UglifyJS + if (!this.isMapped) { + uglify.call(this, this.source, 'UglifyJS', onUglify.bind(this)); + } else { + onComplete.call(this); } - this.compiled.advanced.gzip = result; - - // next, minify the source using only UglifyJS - uglify.call(this, this.source, 'UglifyJS', onUglify.bind(this)); } /** @@ -292,7 +542,7 @@ } result = postprocess(result); this.uglified.source = result; - gzip(result, onUglifyGzip.bind(this)); + zlib.gzip(result, onUglifyGzip.bind(this)); } /** @@ -306,13 +556,23 @@ if (exception) { throw exception; } - if (!this.isSilent) { - console.log('Done. Size: %d bytes.', result.length); + if (result != null) { + if (!this.isSilent) { + console.log('Done. Size: %d bytes.', result.length); + } + this.uglified.gzip = result; + } + // minify the already Closure Compiler simple optimized source using UglifyJS + var modes = this.modes; + if (modes.indexOf('hybrid') > -1) { + if (modes.indexOf('simple') > -1) { + uglify.call(this, this.compiled.simple.source, 'hybrid (simple)', onSimpleHybrid.bind(this)); + } else if (modes.indexOf('advanced') > -1) { + onSimpleHybridGzip.call(this); + } + } else { + onComplete.call(this); } - this.uglified.gzip = result; - - // next, minify the already Closure Compiler simple optimized source using UglifyJS - uglify.call(this, this.compiled.simple.source, 'hybrid (simple)', onSimpleHybrid.bind(this)); } /** @@ -328,7 +588,7 @@ } result = postprocess(result); this.hybrid.simple.source = result; - gzip(result, onSimpleHybridGzip.bind(this)); + zlib.gzip(result, onSimpleHybridGzip.bind(this)); } /** @@ -342,13 +602,18 @@ if (exception) { throw exception; } - if (!this.isSilent) { - console.log('Done. Size: %d bytes.', result.length); + if (result != null) { + if (!this.isSilent) { + console.log('Done. Size: %d bytes.', result.length); + } + this.hybrid.simple.gzip = result; + } + // minify the already Closure Compiler advance optimized source using UglifyJS + if (this.modes.indexOf('advanced') > -1) { + uglify.call(this, this.compiled.advanced.source, 'hybrid (advanced)', onAdvancedHybrid.bind(this)); + } else { + onComplete.call(this); } - this.hybrid.simple.gzip = result; - - // next, minify the already Closure Compiler advance optimized source using UglifyJS - uglify.call(this, this.compiled.advanced.source, 'hybrid (advanced)', onAdvancedHybrid.bind(this)); } /** @@ -364,7 +629,7 @@ } result = postprocess(result); this.hybrid.advanced.source = result; - gzip(result, onAdvancedHybridGzip.bind(this)); + zlib.gzip(result, onAdvancedHybridGzip.bind(this)); } /** @@ -378,11 +643,12 @@ if (exception) { throw exception; } - if (!this.isSilent) { - console.log('Done. Size: %d bytes.', result.length); + if (result != null) { + if (!this.isSilent) { + console.log('Done. Size: %d bytes.', result.length); + } + this.hybrid.advanced.gzip = result; } - this.hybrid.advanced.gzip = result; - // finish by choosing the smallest compressed file onComplete.call(this); } @@ -399,20 +665,32 @@ hybridSimple = this.hybrid.simple, hybridAdvanced = this.hybrid.advanced; + var objects = [ + compiledSimple, + compiledAdvanced, + uglified, + hybridSimple, + hybridAdvanced + ]; + + var gzips = objects + .map(function(data) { return data.gzip; }) + .filter(Boolean); + // select the smallest gzipped file and use its minified counterpart as the // official minified release (ties go to the Closure Compiler) - var min = Math.min( - compiledSimple.gzip.length, - compiledAdvanced.gzip.length, - uglified.gzip.length, - hybridSimple.gzip.length, - hybridAdvanced.gzip.length - ); + var min = gzips.reduce(function(min, gzip) { + var length = gzip.length; + return min > length ? length : min; + }, Infinity); // pass the minified source to the "onComplete" callback - [compiledSimple, compiledAdvanced, uglified, hybridSimple, hybridAdvanced].some(function(data) { - if (data.gzip.length == min) { - this.onComplete(data.source); + objects.some(function(data) { + var gzip = data.gzip; + if (gzip && gzip.length == min) { + data.outputPath = this.outputPath; + this.onComplete(data); + return true; } }, this); } diff --git a/build/post-compile.js b/build/post-compile.js index 6ea6cda9f2..db56866f83 100644 --- a/build/post-compile.js +++ b/build/post-compile.js @@ -6,15 +6,13 @@ var fs = require('fs'); /** The minimal license/copyright template */ - var licenseTemplate = { - 'lodash': - '/*!\n' + - ' Lo-Dash <%= VERSION %> lodash.com/license\n' + - ' Underscore.js 1.4.3 underscorejs.org/LICENSE\n' + - '*/', - 'underscore': - '/*! Underscore.js <%= VERSION %> underscorejs.org/LICENSE */' - }; + var licenseTemplate = [ + '/**', + ' * @license', + ' * Lo-Dash <%= VERSION %> lodash.com/license', + ' * Underscore.js 1.4.4 underscorejs.org/LICENSE', + ' */' + ].join('\n'); /*--------------------------------------------------------------------------*/ @@ -26,36 +24,35 @@ * @returns {String} Returns the processed source. */ function postprocess(source) { - // move vars exposed by the Closure Compiler into the IIFE - source = source.replace(/^((?:(['"])use strict\2;)?(?:var (?:[a-z]+=(?:!0|!1|null)[,;])+)?)([\s\S]*?function[^)]+\){)/, '$3$1'); + // remove copyright header + source = source.replace(/^\/\**[\s\S]+?\*\/\n/, ''); // correct overly aggressive Closure Compiler advanced optimizations - source = source.replace(/prototype\s*=\s*{\s*valueOf\s*:\s*1\s*}/, 'prototype={valueOf:1,y:1}'); - - // unescape properties (e.g. foo["bar"] => foo.bar) - source = source.replace(/(\w)\["([^."]+)"\]/g, function(match, left, right) { - return /\W/.test(right) ? match : (left + '.' + right); - }); + source = source + .replace(/prototype\s*=\s*{\s*valueOf\s*:\s*1\s*}/, 'prototype={valueOf:1,y:1}') + .replace(/(document[^&]+&&)\s*(?:\w+|!\d)/, '$1!({toString:0}+"")'); // flip `typeof` expressions to help optimize Safari and // correct the AMD module definition for AMD build optimizers // (e.g. from `"number" == typeof x` to `typeof x == "number") - source = source.replace(/(return)?("[^"]+")\s*([!=]=)\s*(typeof(?:\s*\([^)]+\)|\s+[\w.]+))/g, function(match, ret, type, equality, expression) { - return (ret ? ret + ' ' : '') + expression + equality + type; + source = source.replace(/(\w)?("[^"]+")\s*([!=]=)\s*(typeof(?:\s*\([^)]+\)|\s+[.\w]+(?!\[)))/g, function(match, other, type, equality, expression) { + return (other ? other + ' ' : '') + expression + equality + type; }); // add trailing semicolon if (source) { - source = source.replace(/[\s;]*$/, ';'); + source = source.replace(/[\s;]*?(\s*\/\/.*\s*|\s*\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/\s*)*$/, ';$1'); } // exit early if version snippet isn't found var snippet = /VERSION\s*[=:]\s*([\'"])(.*?)\1/.exec(source); if (!snippet) { return source; } - // add copyright/license header - return licenseTemplate[/call\(this\);?$/.test(source) ? 'underscore' : 'lodash'] - .replace('<%= VERSION %>', snippet[2]) + '\n;' + source; + // add new copyright header + var version = snippet[2]; + source = licenseTemplate.replace('<%= VERSION %>', version) + '\n;' + source; + + return source; } /*--------------------------------------------------------------------------*/ diff --git a/build/post-install.js b/build/post-install.js deleted file mode 100644 index 8b59affce7..0000000000 --- a/build/post-install.js +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env node -;(function() { - 'use strict'; - - /** Load Node modules */ - var exec = require('child_process').exec, - fs = require('fs'), - https = require('https'), - path = require('path'), - tar = require('../vendor/tar/tar.js'), - zlib = require('zlib'); - - /** The path of the directory that is the base of the repository */ - var basePath = fs.realpathSync(path.join(__dirname, '..')); - - /** The path of the `vendor` directory */ - var vendorPath = path.join(basePath, 'vendor'); - - /** The Git object ID of `closure-compiler.tar.gz` */ - var closureId = 'a2787b470c577cee2404d186c562dd9835f779f5'; - - /** The Git object ID of `uglifyjs.tar.gz` */ - var uglifyId = '7ecae09d413eb48dd5785fe877f24e60ac3bbcef'; - - /** The media type for raw blob data */ - var mediaType = 'application/vnd.github.v3.raw'; - - /** Reassign `existsSync` for older versions of Node */ - fs.existsSync || (fs.existsSync = path.existsSync); - - /** Used to reference parts of the blob href */ - var location = (function() { - var host = 'api.github.com', - origin = 'https://api.github.com', - pathname = '/repos/bestiejs/lodash/git/blobs'; - - return { - 'host': host, - 'href': host + origin + pathname, - 'origin': origin, - 'pathname': pathname - }; - }()); - - /*--------------------------------------------------------------------------*/ - - /** - * Fetches a required `.tar.gz` dependency with the given Git object ID from - * the Lo-Dash repo on GitHub. The object ID may be obtained by running - * `git hash-object path/to/dependency.tar.gz`. - * - * @private - * @param {Object} options The options object. - * id - The Git object ID of the `.tar.gz` file. - * onComplete - The function, invoked with one argument (exception), - * called once the extraction has finished. - * path - The path of the extraction directory. - * title - The dependency's title used in status updates logged to the console. - */ - function getDependency(options) { - options || (options = {}); - - var id = options.id, - onComplete = options.onComplete, - path = options.path, - title = options.title; - - function callback(exception) { - if (exception) { - console.error([ - 'There was a problem installing ' + title + '. To manually install, run:', - '', - "curl -H 'Accept: " + mediaType + "' " + location.href + '/' + id + " | tar xvz -C '" + path + "'" - ].join('\n')); - } - onComplete(exception); - } - - console.log('Downloading ' + title + '...'); - - https.get({ - 'host': location.host, - 'path': location.pathname + '/' + id, - 'headers': { - // By default, all GitHub blob API endpoints return a JSON document - // containing Base64-encoded blob data. Overriding the `Accept` header - // with the GitHub raw media type returns the blob data directly. - // See http://developer.github.com/v3/media/. - 'Accept': mediaType - } - }, function(response) { - var decompressor = zlib.createUnzip(), - parser = new tar.Extract({ 'path': path }); - - decompressor.on('error', callback) - parser.on('end', callback).on('error', callback); - response.pipe(decompressor).pipe(parser); - }) - .on('error', callback); - } - - /*--------------------------------------------------------------------------*/ - - exec('npm -g root', function(exception, stdout) { - if (!exception) { - try { - var root = stdout.trim(), - isGlobal = fs.existsSync(root) && path.resolve(basePath, '..') == fs.realpathSync(root); - } catch(e) { - exception = e; - } - } - if (exception) { - console.error([ - 'Oops! There was a problem detecting the install mode. If you’re installing the', - 'Lo-Dash command-line executable (via `npm install -g lodash`), you’ll need to', - 'manually install UglifyJS and the Closure Compiler by running:', - '', - "curl -H 'Accept: " + mediaType + "' " + location.href + '/' + closureId + " | tar xvz -C '" + vendorPath + "'", - "curl -H 'Accept: " + mediaType + "' " + location.href + '/' + uglifyId + " | tar xvz -C '" + vendorPath + "'", - '', - 'Please submit an issue on the GitHub issue tracker: ' + process.env.npm_package_bugs_url - ].join('\n')); - - console.error(exception); - } - if (!isGlobal) { - return; - } - // download the Closure Compiler - getDependency({ - 'title': 'the Closure Compiler', - 'id': closureId, - 'path': vendorPath, - 'onComplete': function() { - // download UglifyJS - getDependency({ - 'title': 'UglifyJS', - 'id': uglifyId, - 'path': vendorPath, - 'onComplete': function() { - process.exit(); - } - }); - } - }); - }); -}()); diff --git a/build/pre-compile.js b/build/pre-compile.js index 6bcc7ea18f..67097afc3a 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -7,6 +7,7 @@ /** Used to minify variables embedded in compiled strings */ var compiledVars = [ + 'args', 'argsIndex', 'argsLength', 'callback', @@ -17,15 +18,15 @@ 'hasOwnProperty', 'index', 'isArguments', + 'isArray', 'isString', - 'iteratee', + 'iterable', 'length', 'nativeKeys', 'object', 'objectTypes', 'ownIndex', 'ownProps', - 'propertyIsEnumerable', 'result', 'skipProto', 'source', @@ -35,18 +36,18 @@ /** Used to minify `compileIterator` option properties */ var iteratorOptions = [ 'args', - 'arrayLoop', + 'arrays', 'bottom', 'firstArg', 'hasDontEnumBug', + 'hasEnumPrototype', 'isKeysFast', - 'objectLoop', + 'loop', 'nonEnumArgs', 'noCharByIndex', 'shadowed', 'top', - 'useHas', - 'useStrict' + 'useHas' ]; /** Used to minify variables and string values to a single character */ @@ -64,6 +65,7 @@ 'amd', 'any', 'assign', + 'at', 'attachEvent', 'bind', 'bindAll', @@ -104,6 +106,7 @@ 'groupBy', 'has', 'head', + 'imports', 'identity', 'include', 'index', @@ -150,6 +153,7 @@ 'opera', 'pairs', 'partial', + 'partialRight', 'pick', 'pluck', 'random', @@ -191,10 +195,7 @@ // properties used by the `backbone` and `underscore` builds '__chain__', 'chain', - - // properties used by underscore.js - '_chain', - '_wrapped' + 'findWhere' ]; /*--------------------------------------------------------------------------*/ @@ -216,9 +217,6 @@ if (options.isTemplate) { return source; } - // remove copyright/license header to add later in post-compile.js - source = source.replace(/\/\*![\s\S]+?\*\//, ''); - // add brackets to whitelisted properties so the Closure Compiler won't mung them // http://code.google.com/closure/compiler/docs/api-tutorial3.html#export source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), function(match, prop) { @@ -228,9 +226,6 @@ // remove brackets from `_.escape()` in `_.template` source = source.replace(/__e *= *_\['escape']/g, '__e=_.escape'); - // remove brackets from `_.escape()` in underscore.js `_.template` - source = source.replace(/_\['escape'\]\(__t'/g, '_.escape(__t'); - // remove brackets from `collection.indexOf` in `_.contains` source = source.replace("collection['indexOf'](target)", 'collection.indexOf(target)'); @@ -244,19 +239,13 @@ string = string.slice(captured.length); } // avoids removing the '\n' of the `stringEscapes` object - string = string.replace(/\[object |delete |else |function | in |return\s+[\w"']|throw |typeof |use strict|var |@ |(["'])\\n\1|\\\\n|\\n|\s+/g, function(match) { + string = string.replace(/\[object |delete |else (?!{)|function | in |return\s+[\w"']|throw |typeof |use strict|var |@ |(["'])\\n\1|\\\\n|\\n|\s+/g, function(match) { return match == false || match == '\\n' ? '' : match; }); // prepend object literal property name return (captured || '') + string; }); - // add newline to `+"__p+='"` in underscore.js `_.template` - source = source.replace(/\+"__p\+='"/g, '+"\\n__p+=\'"'); - - // add newline to `body + '}'` in `createFunction` - source = source.replace(/body *\+ *'}'/, 'body+"\\n}"'); - // remove whitespace from `_.template` related regexes source = source.replace(/(?:reEmptyString\w+|reInsertVariable) *=.+/g, function(match) { return match.replace(/ |\\n/g, ''); @@ -267,9 +256,6 @@ .replace('"__p += \'"', '"__p+=\'"') .replace('"\';\n"', '"\';"') - // remove `useSourceURL` variable - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *\{(?:\s*\/\/.*)*\n *var useSourceURL[\s\S]+?catch[^}]+}\n/, ''); - // remove debug sourceURL use in `_.template` source = source.replace(/(?:\s*\/\/.*\n)* *var sourceURL[^;]+;|\+ *sourceURL/g, ''); @@ -286,14 +272,15 @@ // minify properties properties.forEach(function(property, index) { - var reBracketProp = RegExp("\\['(" + property + ")'\\]", 'g'), + var minName = minNames[index], + reBracketProp = RegExp("\\['(" + property + ")'\\]", 'g'), reDotProp = RegExp('\\.' + property + '\\b', 'g'), rePropColon = RegExp("([^?\\s])\\s*([\"'])?\\b" + property + "\\2 *:", 'g'); modified = modified - .replace(reBracketProp, "['" + minNames[index] + "']") - .replace(reDotProp, "['" + minNames[index] + "']") - .replace(rePropColon, "$1'" + minNames[index] + "':"); + .replace(reBracketProp, "['" + minName + "']") + .replace(reDotProp, "['" + minName + "']") + .replace(rePropColon, "$1'" + minName + "':"); }); // replace with modified snippet @@ -331,36 +318,30 @@ }); if (isCreateIterator) { - // replace with modified snippet early and clip snippet to the `factory` - // call so other arguments aren't minified + // clip before the `factory` call to avoid minifying its arguments source = source.replace(snippet, modified); - snippet = modified = modified.replace(/factory\([\s\S]+$/, ''); + snippet = modified = modified.replace(/return factory\([\s\S]+$/, ''); } + // minify `createIterator` option property names + iteratorOptions.forEach(function(property, index) { + var minName = minNames[index]; + + // minify variables in `iteratorTemplate` or property names in everything else + modified = isIteratorTemplate + ? modified.replace(RegExp('\\b' + property + '\\b', 'g'), minName) + : modified.replace(RegExp("'" + property + "'", 'g'), "'" + minName + "'"); + }); // minify snippet variables / arguments compiledVars.forEach(function(variable, index) { + var minName = minNames[index]; + // ensure properties in compiled strings aren't minified - modified = modified.replace(RegExp('([^.]\\b)' + variable + '\\b(?!\' *[\\]:])', 'g'), '$1' + minNames[index]); + modified = modified.replace(RegExp('([^.]\\b)' + variable + '\\b(?!\' *[\\]:])', 'g'), '$1' + minName); // correct `typeof` values if (/^(?:boolean|function|object|number|string|undefined)$/.test(variable)) { - modified = modified.replace(RegExp("(typeof [^']+')" + minNames[index] + "'", 'g'), '$1' + variable + "'"); - } - }); - - // minify `createIterator` option property names - iteratorOptions.forEach(function(property, index) { - if (isIteratorTemplate) { - // minify property names as interpolated template variables - modified = modified.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]); - } - else { - // minify property name strings - modified = modified.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'"); - // minify property names in accessors - if (isCreateIterator) { - modified = modified.replace(RegExp('\\.' + property + '\\b' , 'g'), '.' + minNames[index]); - } + modified = modified.replace(RegExp("(typeof [^']+')" + minName + "'", 'g'), '$1' + variable + "'"); } }); diff --git a/dist/lodash.compat.js b/dist/lodash.compat.js new file mode 100644 index 0000000000..7fcf2b3999 --- /dev/null +++ b/dist/lodash.compat.js @@ -0,0 +1,5138 @@ +/** + * @license + * Lo-Dash 1.0.0 (Custom Build) + * Build: `lodash -o ./dist/lodash.compat.js` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.4.4 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. + * Available under MIT license + */ +;(function(window, undefined) { + + /** Detect free variable `exports` */ + var freeExports = typeof exports == 'object' && exports; + + /** Detect free variable `global` and use it as `window` */ + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal) { + window = freeGlobal; + } + + /** Used for array and object method references */ + var arrayRef = [], + objectRef = {}; + + /** Used to generate unique IDs */ + var idCounter = 0; + + /** Used internally to indicate various things */ + var indicatorObject = objectRef; + + /** Used by `cachedContains` as the default size when optimizations are enabled for large arrays */ + var largeArraySize = 30; + + /** Used to restore the original `_` reference in `noConflict` */ + var oldDash = window._; + + /** Used to match HTML entities */ + var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g; + + /** Used to match empty string literals in compiled template source */ + var reEmptyStringLeading = /\b__p \+= '';/g, + reEmptyStringMiddle = /\b(__p \+=) '' \+/g, + reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; + + /** Used to match regexp flags from their coerced string values */ + var reFlags = /\w*$/; + + /** Used to detect if a method is native */ + var reNative = RegExp('^' + + (objectRef.valueOf + '') + .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + .replace(/valueOf|for [^\]]+/g, '.+?') + '$' + ); + + /** + * Used to match ES6 template delimiters + * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-7.8.6 + */ + var reEsTemplate = /\$\{((?:(?=\\?)\\?[\s\S])*?)\}/g; + + /** Used to match "interpolate" template delimiters */ + var reInterpolate = /<%=([\s\S]+?)%>/g; + + /** Used to ensure capturing order of template delimiters */ + var reNoMatch = /($^)/; + + /** Used to match HTML characters */ + var reUnescapedHtml = /[&<>"']/g; + + /** Used to match unescaped characters in compiled string literals */ + var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g; + + /** Used to fix the JScript [[DontEnum]] bug */ + var shadowed = [ + 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', + 'toLocaleString', 'toString', 'valueOf' + ]; + + /** Used to make template sourceURLs easier to identify */ + var templateCounter = 0; + + /** Native method shortcuts */ + var ceil = Math.ceil, + concat = arrayRef.concat, + floor = Math.floor, + getPrototypeOf = reNative.test(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf, + hasOwnProperty = objectRef.hasOwnProperty, + push = arrayRef.push, + toString = objectRef.toString; + + /* Native method shortcuts for methods with the same name as other `lodash` methods */ + var nativeBind = reNative.test(nativeBind = slice.bind) && nativeBind, + nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, + nativeIsFinite = window.isFinite, + nativeIsNaN = window.isNaN, + nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys, + nativeMax = Math.max, + nativeMin = Math.min, + nativeRandom = Math.random; + + /** `Object#toString` result shortcuts */ + var argsClass = '[object Arguments]', + arrayClass = '[object Array]', + boolClass = '[object Boolean]', + dateClass = '[object Date]', + funcClass = '[object Function]', + numberClass = '[object Number]', + objectClass = '[object Object]', + regexpClass = '[object RegExp]', + stringClass = '[object String]'; + + /** Detect various environments */ + var isIeOpera = !!window.attachEvent, + isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera); + + /* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */ + var isBindFast = nativeBind && !isV8; + + /* Detect if `Object.keys` exists and is inferred to be fast (IE, Opera, V8) */ + var isKeysFast = nativeKeys && (isIeOpera || isV8); + + /** + * Detect the JScript [[DontEnum]] bug: + * + * In IE < 9 an objects own properties, shadowing non-enumerable ones, are + * made non-enumerable as well. + */ + var hasDontEnumBug; + + /** + * Detect if a `prototype` properties are enumerable by default: + * + * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 + * (if the prototype or a property on the prototype has been set) + * incorrectly sets a function's `prototype` property [[Enumerable]] + * value to `true`. + */ + var hasEnumPrototype; + + /** Detect if own properties are iterated after inherited properties (IE < 9) */ + var iteratesOwnLast; + + /** + * Detect if `Array#shift` and `Array#splice` augment array-like objects + * incorrectly: + * + * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()` + * and `splice()` functions that fail to remove the last element, `value[0]`, + * of array-like objects even though the `length` property is set to `0`. + * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` + * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. + */ + var hasObjectSpliceBug = (hasObjectSpliceBug = { '0': 1, 'length': 1 }, + arrayRef.splice.call(hasObjectSpliceBug, 0, 1), hasObjectSpliceBug[0]); + + /** Detect if `arguments` object indexes are non-enumerable (Firefox < 4, IE < 9, PhantomJS, Safari < 5.1) */ + var nonEnumArgs = true; + + (function() { + var props = []; + function ctor() { this.x = 1; } + ctor.prototype = { 'valueOf': 1, 'y': 1 }; + for (var prop in new ctor) { props.push(prop); } + for (prop in arguments) { nonEnumArgs = !prop; } + + hasDontEnumBug = !/valueOf/.test(props); + hasEnumPrototype = ctor.propertyIsEnumerable('prototype'); + iteratesOwnLast = props[0] != 'x'; + }(1)); + + /** Detect if `arguments` objects are `Object` objects (all but Opera < 10.5) */ + var argsAreObjects = arguments.constructor == Object; + + /** Detect if `arguments` objects [[Class]] is unresolvable (Firefox < 4, IE < 9) */ + var noArgsClass = !isArguments(arguments); + + /** + * Detect lack of support for accessing string characters by index: + * + * IE < 8 can't access characters by index and IE 8 can only access + * characters by index on string literals. + */ + var noCharByIndex = ('x'[0] + Object('x')[0]) != 'xx'; + + /** + * Detect if a node's [[Class]] is unresolvable (IE < 9) + * and that the JS engine won't error when attempting to coerce an object to + * a string without a `toString` function. + */ + try { + var noNodeClass = toString.call(document) == objectClass && !({ 'toString': 0 } + ''); + } catch(e) { } + + /** Used to identify object classifications that `_.clone` supports */ + var cloneableClasses = {}; + cloneableClasses[funcClass] = false; + cloneableClasses[argsClass] = cloneableClasses[arrayClass] = + cloneableClasses[boolClass] = cloneableClasses[dateClass] = + cloneableClasses[numberClass] = cloneableClasses[objectClass] = + cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true; + + /** Used to lookup a built-in constructor by [[Class]] */ + var ctorByClass = {}; + ctorByClass[arrayClass] = Array; + ctorByClass[boolClass] = Boolean; + ctorByClass[dateClass] = Date; + ctorByClass[objectClass] = Object; + ctorByClass[numberClass] = Number; + ctorByClass[regexpClass] = RegExp; + ctorByClass[stringClass] = String; + + /** Used to determine if values are of the language type Object */ + var objectTypes = { + 'boolean': false, + 'function': true, + 'object': true, + 'number': false, + 'string': false, + 'undefined': false + }; + + /** Used to escape characters for inclusion in compiled string literals */ + var stringEscapes = { + '\\': '\\', + "'": "'", + '\n': 'n', + '\r': 'r', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + /*--------------------------------------------------------------------------*/ + + /** + * Creates a `lodash` object, that wraps the given `value`, to enable method + * chaining. + * + * In addition to Lo-Dash methods, wrappers also have the following `Array` methods: + * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`, + * and `unshift` + * + * The chainable wrapper functions are: + * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, `compose`, + * `concat`, `countBy`, `debounce`, `defaults`, `defer`, `delay`, `difference`, + * `filter`, `flatten`, `forEach`, `forIn`, `forOwn`, `functions`, `groupBy`, + * `initial`, `intersection`, `invert`, `invoke`, `keys`, `map`, `max`, `memoize`, + * `merge`, `min`, `object`, `omit`, `once`, `pairs`, `partial`, `partialRight`, + * `pick`, `pluck`, `push`, `range`, `reject`, `rest`, `reverse`, `shuffle`, + * `slice`, `sort`, `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, + * `union`, `uniq`, `unshift`, `values`, `where`, `without`, `wrap`, and `zip` + * + * The non-chainable wrapper functions are: + * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `has`, `identity`, + * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`, `isEmpty`, + * `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`, `isObject`, + * `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`, `lastIndexOf`, + * `mixin`, `noConflict`, `pop`, `random`, `reduce`, `reduceRight`, `result`, + * `shift`, `size`, `some`, `sortedIndex`, `template`, `unescape`, and `uniqueId` + * + * The wrapper functions `first` and `last` return wrapped values when `n` is + * passed, otherwise they return unwrapped values. + * + * @name _ + * @constructor + * @category Chaining + * @param {Mixed} value The value to wrap in a `lodash` instance. + * @returns {Object} Returns a `lodash` instance. + */ + function lodash(value) { + // exit early if already wrapped, even if wrapped by a different `lodash` constructor + if (value && typeof value == 'object' && value.__wrapped__) { + return value; + } + // allow invoking `lodash` without the `new` operator + if (!(this instanceof lodash)) { + return new lodash(value); + } + this.__wrapped__ = value; + } + + /** + * By default, the template delimiters used by Lo-Dash are similar to those in + * embedded Ruby (ERB). Change the following template settings to use alternative + * delimiters. + * + * @static + * @memberOf _ + * @type Object + */ + lodash.templateSettings = { + + /** + * Used to detect `data` property values to be HTML-escaped. + * + * @memberOf _.templateSettings + * @type RegExp + */ + 'escape': /<%-([\s\S]+?)%>/g, + + /** + * Used to detect code to be evaluated. + * + * @memberOf _.templateSettings + * @type RegExp + */ + 'evaluate': /<%([\s\S]+?)%>/g, + + /** + * Used to detect `data` property values to inject. + * + * @memberOf _.templateSettings + * @type RegExp + */ + 'interpolate': reInterpolate, + + /** + * Used to reference the data object in the template text. + * + * @memberOf _.templateSettings + * @type String + */ + 'variable': '', + + /** + * Used to import variables into the compiled template. + * + * @memberOf _.templateSettings + * @type Object + */ + 'imports': { + + /** + * A reference to the `lodash` function. + * + * @memberOf _.templateSettings.imports + * @type Function + */ + '_': lodash + } + }; + + /*--------------------------------------------------------------------------*/ + + /** + * The template used to create iterator functions. + * + * @private + * @param {Obect} data The data object used to populate the text. + * @returns {String} Returns the interpolated text. + */ + var iteratorTemplate = function(obj) { + + var __p = 'var index, iterable = ' + + (obj.firstArg ) + + ', result = iterable;\nif (!iterable) return result;\n' + + (obj.top ) + + ';\n'; + if (obj.arrays) { + __p += 'var length = iterable.length; index = -1;\nif (' + + (obj.arrays ) + + ') { '; + if (obj.noCharByIndex) { + __p += '\n if (isString(iterable)) {\n iterable = iterable.split(\'\')\n } '; + } ; + __p += '\n while (++index < length) {\n ' + + (obj.loop ) + + '\n }\n}\nelse { '; + } else if (obj.nonEnumArgs) { + __p += '\n var length = iterable.length; index = -1;\n if (length && isArguments(iterable)) {\n while (++index < length) {\n index += \'\';\n ' + + (obj.loop ) + + '\n }\n } else { '; + } ; + + if (obj.hasEnumPrototype) { + __p += '\n var skipProto = typeof iterable == \'function\';\n '; + } ; + + if (obj.isKeysFast && obj.useHas) { + __p += '\n var ownIndex = -1,\n ownProps = objectTypes[typeof iterable] ? nativeKeys(iterable) : [],\n length = ownProps.length;\n\n while (++ownIndex < length) {\n index = ownProps[ownIndex];\n '; + if (obj.hasEnumPrototype) { + __p += 'if (!(skipProto && index == \'prototype\')) {\n '; + } ; + __p += + (obj.loop ) + + ''; + if (obj.hasEnumPrototype) { + __p += '}\n'; + } ; + __p += ' } '; + } else { + __p += '\n for (index in iterable) {'; + if (obj.hasEnumPrototype || obj.useHas) { + __p += '\n if ('; + if (obj.hasEnumPrototype) { + __p += '!(skipProto && index == \'prototype\')'; + } if (obj.hasEnumPrototype && obj.useHas) { + __p += ' && '; + } if (obj.useHas) { + __p += 'hasOwnProperty.call(iterable, index)'; + } ; + __p += ') { '; + } ; + __p += + (obj.loop ) + + '; '; + if (obj.hasEnumPrototype || obj.useHas) { + __p += '\n }'; + } ; + __p += '\n } '; + } ; + + if (obj.hasDontEnumBug) { + __p += '\n\n var ctor = iterable.constructor;\n '; + for (var k = 0; k < 7; k++) { + __p += '\n index = \'' + + (obj.shadowed[k] ) + + '\';\n if ('; + if (obj.shadowed[k] == 'constructor') { + __p += '!(ctor && ctor.prototype === iterable) && '; + } ; + __p += 'hasOwnProperty.call(iterable, index)) {\n ' + + (obj.loop ) + + '\n } '; + } ; + + } ; + + if (obj.arrays || obj.nonEnumArgs) { + __p += '\n}'; + } ; + __p += + (obj.bottom ) + + ';\nreturn result'; + + + return __p + }; + + /** Reusable iterator options for `assign` and `defaults` */ + var defaultsIteratorOptions = { + 'args': 'object, source, guard', + 'top': + 'var args = arguments,\n' + + ' argsIndex = 0,\n' + + " argsLength = typeof guard == 'number' ? 2 : args.length;\n" + + 'while (++argsIndex < argsLength) {\n' + + ' iterable = args[argsIndex];\n' + + ' if (iterable && objectTypes[typeof iterable]) {', + 'loop': "if (typeof result[index] == 'undefined') result[index] = iterable[index]", + 'bottom': ' }\n}' + }; + + /** Reusable iterator options shared by `each`, `forIn`, and `forOwn` */ + var eachIteratorOptions = { + 'args': 'collection, callback, thisArg', + 'top': "callback = callback && typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg)", + 'arrays': "typeof length == 'number'", + 'loop': 'if (callback(iterable[index], index, collection) === false) return result' + }; + + /** Reusable iterator options for `forIn` and `forOwn` */ + var forOwnIteratorOptions = { + 'top': 'if (!objectTypes[typeof iterable]) return result;\n' + eachIteratorOptions.top, + 'arrays': false + }; + + /*--------------------------------------------------------------------------*/ + + /** + * Creates a function optimized to search large arrays for a given `value`, + * starting at `fromIndex`, using strict equality for comparisons, i.e. `===`. + * + * @private + * @param {Array} array The array to search. + * @param {Mixed} value The value to search for. + * @param {Number} [fromIndex=0] The index to search from. + * @param {Number} [largeSize=30] The length at which an array is considered large. + * @returns {Boolean} Returns `true`, if `value` is found, else `false`. + */ + function cachedContains(array, fromIndex, largeSize) { + fromIndex || (fromIndex = 0); + + var length = array.length, + isLarge = (length - fromIndex) >= (largeSize || largeArraySize); + + if (isLarge) { + var cache = {}, + index = fromIndex - 1; + + while (++index < length) { + // manually coerce `value` to a string because `hasOwnProperty`, in some + // older versions of Firefox, coerces objects incorrectly + var key = array[index] + ''; + (hasOwnProperty.call(cache, key) ? cache[key] : (cache[key] = [])).push(array[index]); + } + } + return function(value) { + if (isLarge) { + var key = value + ''; + return hasOwnProperty.call(cache, key) && indexOf(cache[key], value) > -1; + } + return indexOf(array, value, fromIndex) > -1; + } + } + + /** + * Used by `_.max` and `_.min` as the default `callback` when a given + * `collection` is a string value. + * + * @private + * @param {String} value The character to inspect. + * @returns {Number} Returns the code unit of given character. + */ + function charAtCallback(value) { + return value.charCodeAt(0); + } + + /** + * Used by `sortBy` to compare transformed `collection` values, stable sorting + * them in ascending order. + * + * @private + * @param {Object} a The object to compare to `b`. + * @param {Object} b The object to compare to `a`. + * @returns {Number} Returns the sort order indicator of `1` or `-1`. + */ + function compareAscending(a, b) { + var ai = a.index, + bi = b.index; + + a = a.criteria; + b = b.criteria; + + // ensure a stable sort in V8 and other engines + // http://code.google.com/p/v8/issues/detail?id=90 + if (a !== b) { + if (a > b || typeof a == 'undefined') { + return 1; + } + if (a < b || typeof b == 'undefined') { + return -1; + } + } + return ai < bi ? -1 : 1; + } + + /** + * Creates a function that, when called, invokes `func` with the `this` binding + * of `thisArg` and prepends any `partialArgs` to the arguments passed to the + * bound function. + * + * @private + * @param {Function|String} func The function to bind or the method name. + * @param {Mixed} [thisArg] The `this` binding of `func`. + * @param {Array} partialArgs An array of arguments to be partially applied. + * @param {Object} [rightIndicator] Used to indicate partially applying arguments from the right. + * @returns {Function} Returns the new bound function. + */ + function createBound(func, thisArg, partialArgs, rightIndicator) { + var isFunc = isFunction(func), + isPartial = !partialArgs, + key = thisArg; + + // juggle arguments + if (isPartial) { + partialArgs = thisArg; + } + if (!isFunc) { + thisArg = func; + } + + function bound() { + // `Function#bind` spec + // http://es5.github.com/#x15.3.4.5 + var args = arguments, + thisBinding = isPartial ? this : thisArg; + + if (!isFunc) { + func = thisArg[key]; + } + if (partialArgs.length) { + args = args.length + ? (args = slice(args), rightIndicator ? args.concat(partialArgs) : partialArgs.concat(args)) + : partialArgs; + } + if (this instanceof bound) { + // ensure `new bound` is an instance of `bound` and `func` + noop.prototype = func.prototype; + thisBinding = new noop; + noop.prototype = null; + + // mimic the constructor's `return` behavior + // http://es5.github.com/#x13.2.2 + var result = func.apply(thisBinding, args); + return isObject(result) ? result : thisBinding; + } + return func.apply(thisBinding, args); + } + return bound; + } + + /** + * Produces a callback bound to an optional `thisArg`. If `func` is a property + * name, the created callback will return the property value for a given element. + * If `func` is an object, the created callback will return `true` for elements + * that contain the equivalent object properties, otherwise it will return `false`. + * + * @private + * @param {Mixed} [func=identity] The value to convert to a callback. + * @param {Mixed} [thisArg] The `this` binding of the created callback. + * @param {Number} [argCount=3] The number of arguments the callback accepts. + * @returns {Function} Returns a callback function. + */ + function createCallback(func, thisArg, argCount) { + if (func == null) { + return identity; + } + var type = typeof func; + if (type != 'function') { + if (type != 'object') { + return function(object) { + return object[func]; + }; + } + var props = keys(func); + return function(object) { + var length = props.length, + result = false; + while (length--) { + if (!(result = isEqual(object[props[length]], func[props[length]], indicatorObject))) { + break; + } + } + return result; + }; + } + if (typeof thisArg != 'undefined') { + if (argCount === 1) { + return function(value) { + return func.call(thisArg, value); + }; + } + if (argCount === 2) { + return function(a, b) { + return func.call(thisArg, a, b); + }; + } + if (argCount === 4) { + return function(accumulator, value, index, object) { + return func.call(thisArg, accumulator, value, index, object); + }; + } + return function(value, index, object) { + return func.call(thisArg, value, index, object); + }; + } + return func; + } + + /** + * Creates compiled iteration functions. + * + * @private + * @param {Object} [options1, options2, ...] The compile options object(s). + * arrays - A string of code to determine if the iterable is an array or array-like. + * useHas - A boolean to specify using `hasOwnProperty` checks in the object loop. + * args - A string of comma separated arguments the iteration function will accept. + * top - A string of code to execute before the iteration branches. + * loop - A string of code to execute in the object loop. + * bottom - A string of code to execute after the iteration branches. + * + * @returns {Function} Returns the compiled function. + */ + function createIterator() { + var data = { + // support properties + 'hasDontEnumBug': hasDontEnumBug, + 'hasEnumPrototype': hasEnumPrototype, + 'isKeysFast': isKeysFast, + 'nonEnumArgs': nonEnumArgs, + 'noCharByIndex': noCharByIndex, + 'shadowed': shadowed, + + // iterator options + 'arrays': 'isArray(iterable)', + 'bottom': '', + 'loop': '', + 'top': '', + 'useHas': true + }; + + // merge options into a template data object + for (var object, index = 0; object = arguments[index]; index++) { + for (var key in object) { + data[key] = object[key]; + } + } + var args = data.args; + data.firstArg = /^[^,]+/.exec(args)[0]; + + // create the function factory + var factory = Function( + 'createCallback, hasOwnProperty, isArguments, isArray, isString, ' + + 'objectTypes, nativeKeys', + 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' + ); + // return the compiled function + return factory( + createCallback, hasOwnProperty, isArguments, isArray, isString, + objectTypes, nativeKeys + ); + } + + /** + * A function compiled to iterate `arguments` objects, arrays, objects, and + * strings consistenly across environments, executing the `callback` for each + * element in the `collection`. The `callback` is bound to `thisArg` and invoked + * with three arguments; (value, index|key, collection). Callbacks may exit + * iteration early by explicitly returning `false`. + * + * @private + * @type Function + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array|Object|String} Returns `collection`. + */ + var each = createIterator(eachIteratorOptions); + + /** + * Used by `template` to escape characters for inclusion in compiled + * string literals. + * + * @private + * @param {String} match The matched character to escape. + * @returns {String} Returns the escaped character. + */ + function escapeStringChar(match) { + return '\\' + stringEscapes[match]; + } + + /** + * Used by `escape` to convert characters to HTML entities. + * + * @private + * @param {String} match The matched character to escape. + * @returns {String} Returns the escaped character. + */ + function escapeHtmlChar(match) { + return htmlEscapes[match]; + } + + /** + * Checks if `value` is a DOM node in IE < 9. + * + * @private + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a DOM node, else `false`. + */ + function isNode(value) { + // IE < 9 presents DOM nodes as `Object` objects except they have `toString` + // methods that are `typeof` "string" and still can coerce nodes to strings + return typeof value.toString != 'function' && typeof (value + '') == 'string'; + } + + /** + * A no-operation function. + * + * @private + */ + function noop() { + // no operation performed + } + + /** + * Slices the `collection` from the `start` index up to, but not including, + * the `end` index. + * + * Note: This function is used, instead of `Array#slice`, to support node lists + * in IE < 9 and to ensure dense arrays are returned. + * + * @private + * @param {Array|Object|String} collection The collection to slice. + * @param {Number} start The start index. + * @param {Number} end The end index. + * @returns {Array} Returns the new array. + */ + function slice(array, start, end) { + start || (start = 0); + if (typeof end == 'undefined') { + end = array ? array.length : 0; + } + var index = -1, + length = end - start || 0, + result = Array(length < 0 ? 0 : length); + + while (++index < length) { + result[index] = array[start + index]; + } + return result; + } + + /** + * Used by `unescape` to convert HTML entities to characters. + * + * @private + * @param {String} match The matched character to unescape. + * @returns {String} Returns the unescaped character. + */ + function unescapeHtmlChar(match) { + return htmlUnescapes[match]; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Checks if `value` is an `arguments` object. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is an `arguments` object, else `false`. + * @example + * + * (function() { return _.isArguments(arguments); })(1, 2, 3); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + function isArguments(value) { + return toString.call(value) == argsClass; + } + // fallback for browsers that can't detect `arguments` objects by [[Class]] + if (noArgsClass) { + isArguments = function(value) { + return value ? hasOwnProperty.call(value, 'callee') : false; + }; + } + + /** + * Iterates over `object`'s own and inherited enumerable properties, executing + * the `callback` for each property. The `callback` is bound to `thisArg` and + * invoked with three arguments; (value, key, object). Callbacks may exit iteration + * early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @type Function + * @category Objects + * @param {Object} object The object to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns `object`. + * @example + * + * function Dog(name) { + * this.name = name; + * } + * + * Dog.prototype.bark = function() { + * alert('Woof, woof!'); + * }; + * + * _.forIn(new Dog('Dagny'), function(value, key) { + * alert(key); + * }); + * // => alerts 'name' and 'bark' (order is not guaranteed) + */ + var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, { + 'useHas': false + }); + + /** + * Iterates over an object's own enumerable properties, executing the `callback` + * for each property. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, key, object). Callbacks may exit iteration early by explicitly + * returning `false`. + * + * @static + * @memberOf _ + * @type Function + * @category Objects + * @param {Object} object The object to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns `object`. + * @example + * + * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { + * alert(key); + * }); + * // => alerts '0', '1', and 'length' (order is not guaranteed) + */ + var forOwn = createIterator(eachIteratorOptions, forOwnIteratorOptions); + + /** + * Checks if `value` is an array. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is an array, else `false`. + * @example + * + * (function() { return _.isArray(arguments); })(); + * // => false + * + * _.isArray([1, 2, 3]); + * // => true + */ + var isArray = nativeIsArray || function(value) { + // `instanceof` may cause a memory leak in IE 7 if `value` is a host object + // http://ajaxian.com/archives/working-aroung-the-instanceof-memory-leak + return (argsAreObjects && value instanceof Array) || toString.call(value) == arrayClass; + }; + + /** + * Creates an array composed of the own enumerable property names of `object`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names. + * @example + * + * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); + * // => ['one', 'two', 'three'] (order is not guaranteed) + */ + var keys = !nativeKeys ? shimKeys : function(object) { + if (!isObject(object)) { + return []; + } + if ((hasEnumPrototype && typeof object == 'function') || + (nonEnumArgs && object.length && isArguments(object))) { + return shimKeys(object); + } + return nativeKeys(object); + }; + + /** + * A fallback implementation of `isPlainObject` that checks if a given `value` + * is an object created by the `Object` constructor, assuming objects created + * by the `Object` constructor have no inherited enumerable properties and that + * there are no `Object.prototype` extensions. + * + * @private + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if `value` is a plain object, else `false`. + */ + function shimIsPlainObject(value) { + // avoid non-objects and false positives for `arguments` objects + var result = false; + if (!(value && typeof value == 'object') || isArguments(value)) { + return result; + } + // check that the constructor is `Object` (i.e. `Object instanceof Object`) + var ctor = value.constructor; + if ((!isFunction(ctor) && (!noNodeClass || !isNode(value))) || ctor instanceof ctor) { + // IE < 9 iterates inherited properties before own properties. If the first + // iterated property is an object's own property then there are no inherited + // enumerable properties. + if (iteratesOwnLast) { + forIn(value, function(value, key, object) { + result = !hasOwnProperty.call(object, key); + return false; + }); + return result === false; + } + // In most environments an object's own properties are iterated before + // its inherited properties. If the last iterated property is an object's + // own property then there are no inherited enumerable properties. + forIn(value, function(value, key) { + result = key; + }); + return result === false || hasOwnProperty.call(value, result); + } + return result; + } + + /** + * A fallback implementation of `Object.keys` that produces an array of the + * given object's own enumerable property names. + * + * @private + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names. + */ + function shimKeys(object) { + var result = []; + forOwn(object, function(value, key) { + result.push(key); + }); + return result; + } + + /** + * Used to convert characters to HTML entities: + * + * Though the `>` character is escaped for symmetry, characters like `>` and `/` + * don't require escaping in HTML and have no special meaning unless they're part + * of a tag or an unquoted attribute value. + * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact") + */ + var htmlEscapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + + /** Used to convert HTML entities to characters */ + var htmlUnescapes = invert(htmlEscapes); + + /*--------------------------------------------------------------------------*/ + + /** + * Assigns own enumerable properties of source object(s) to the destination + * object. Subsequent sources will overwrite propery assignments of previous + * sources. If a `callback` function is passed, it will be executed to produce + * the assigned values. The `callback` is bound to `thisArg` and invoked with + * two arguments; (objectValue, sourceValue). + * + * @static + * @memberOf _ + * @type Function + * @alias extend + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @param {Function} [callback] The function to customize assigning values. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the destination object. + * @example + * + * _.assign({ 'name': 'moe' }, { 'age': 40 }); + * // => { 'name': 'moe', 'age': 40 } + * + * var defaults = _.partialRight(_.assign, function(a, b) { + * return typeof a == 'undefined' ? b : a; + * }); + * + * var food = { 'name': 'apple' }; + * defaults(food, { 'name': 'banana', 'type': 'fruit' }); + * // => { 'name': 'apple', 'type': 'fruit' } + */ + var assign = createIterator(defaultsIteratorOptions, { + 'top': + defaultsIteratorOptions.top.replace(';', + ';\n' + + 'if (argsLength > 2) {\n' + + " if (typeof args[argsLength - 2] == 'function') {\n" + + ' var callback = createCallback(args[--argsLength - 1], args[argsLength--], 2);\n' + + " } else if (typeof args[argsLength - 1] == 'function') {\n" + + ' callback = args[--argsLength];\n' + + ' }\n' + + '}' + ), + 'loop': 'result[index] = callback ? callback(result[index], iterable[index]) : iterable[index]' + }); + + /** + * Creates a clone of `value`. If `deep` is `true`, nested objects will also + * be cloned, otherwise they will be assigned by reference. If a `callback` + * function is passed, it will be executed to produce the cloned values. If + * `callback` returns `undefined`, cloning will be handled by the method instead. + * The `callback` is bound to `thisArg` and invoked with one argument; (value). + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to clone. + * @param {Boolean} [deep=false] A flag to indicate a deep clone. + * @param {Function} [callback] The function to customize cloning values. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param- {Array} [stackA=[]] Internally used to track traversed source objects. + * @param- {Array} [stackB=[]] Internally used to associate clones with source counterparts. + * @returns {Mixed} Returns the cloned `value`. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * var shallow = _.clone(stooges); + * shallow[0] === stooges[0]; + * // => true + * + * var deep = _.clone(stooges, true); + * deep[0] === stooges[0]; + * // => false + * + * _.mixin({ + * 'clone': _.partialRight(_.clone, function(value) { + * return _.isElement(value) ? value.cloneNode(false) : undefined; + * }) + * }); + * + * var clone = _.clone(document.body); + * clone.childNodes.length; + * // => 0 + */ + function clone(value, deep, callback, thisArg, stackA, stackB) { + var result = value; + + // allows working with "Collections" methods without using their `callback` + // argument, `index|key`, for this method's `callback` + if (typeof deep == 'function') { + thisArg = callback; + callback = deep; + deep = false; + } + if (typeof callback == 'function') { + callback = typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg, 1); + result = callback(result); + + var done = typeof result != 'undefined'; + if (!done) { + result = value; + } + } + // inspect [[Class]] + var isObj = isObject(result); + if (isObj) { + var className = toString.call(result); + if (!cloneableClasses[className] || (noNodeClass && isNode(result))) { + return result; + } + var isArr = isArray(result); + } + // shallow clone + if (!isObj || !deep) { + return isObj && !done + ? (isArr ? slice(result) : assign({}, result)) + : result; + } + var ctor = ctorByClass[className]; + switch (className) { + case boolClass: + case dateClass: + return done ? result : new ctor(+result); + + case numberClass: + case stringClass: + return done ? result : new ctor(result); + + case regexpClass: + return done ? result : ctor(result.source, reFlags.exec(result)); + } + // check for circular references and return corresponding clone + stackA || (stackA = []); + stackB || (stackB = []); + + var length = stackA.length; + while (length--) { + if (stackA[length] == value) { + return stackB[length]; + } + } + // init cloned object + if (!done) { + result = isArr ? ctor(result.length) : {}; + + // add array properties assigned by `RegExp#exec` + if (isArr) { + if (hasOwnProperty.call(value, 'index')) { + result.index = value.index; + } + if (hasOwnProperty.call(value, 'input')) { + result.input = value.input; + } + } + } + // add the source value to the stack of traversed objects + // and associate it with its clone + stackA.push(value); + stackB.push(result); + + // recursively populate clone (susceptible to call stack limits) + (isArr ? forEach : forOwn)(done ? result : value, function(objValue, key) { + result[key] = clone(objValue, deep, callback, undefined, stackA, stackB); + }); + + return result; + } + + /** + * Creates a deep clone of `value`. If a `callback` function is passed, it will + * be executed to produce the cloned values. If `callback` returns the value it + * was passed, cloning will be handled by the method instead. The `callback` is + * bound to `thisArg` and invoked with one argument; (value). + * + * Note: This function is loosely based on the structured clone algorithm. Functions + * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and + * objects created by constructors other than `Object` are cloned to plain `Object` objects. + * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to deep clone. + * @param {Function} [callback] The function to customize cloning values. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the deep cloned `value`. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * var deep = _.cloneDeep(stooges); + * deep[0] === stooges[0]; + * // => false + * + * var view = { + * 'label': 'docs', + * 'node': element + * }; + * + * var clone = _.cloneDeep(view, function(value) { + * return _.isElement(value) ? value.cloneNode(true) : value; + * }); + * + * clone.node == view.node; + * // => false + */ + function cloneDeep(value, callback, thisArg) { + return clone(value, true, callback, thisArg); + } + + /** + * Assigns own enumerable properties of source object(s) to the destination + * object for all destination properties that resolve to `undefined`. Once a + * property is set, additional defaults of the same property will be ignored. + * + * @static + * @memberOf _ + * @type Function + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @param- {Object} [guard] Internally used to allow working with `_.reduce` + * without using its callback's `key` and `object` arguments as sources. + * @returns {Object} Returns the destination object. + * @example + * + * var food = { 'name': 'apple' }; + * _.defaults(food, { 'name': 'banana', 'type': 'fruit' }); + * // => { 'name': 'apple', 'type': 'fruit' } + */ + var defaults = createIterator(defaultsIteratorOptions); + + /** + * Creates a sorted array of all enumerable properties, own and inherited, + * of `object` that have function values. + * + * @static + * @memberOf _ + * @alias methods + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names that have function values. + * @example + * + * _.functions(_); + * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] + */ + function functions(object) { + var result = []; + forIn(object, function(value, key) { + if (isFunction(value)) { + result.push(key); + } + }); + return result.sort(); + } + + /** + * Checks if the specified object `property` exists and is a direct property, + * instead of an inherited property. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to check. + * @param {String} property The property to check for. + * @returns {Boolean} Returns `true` if key is a direct property, else `false`. + * @example + * + * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); + * // => true + */ + function has(object, property) { + return object ? hasOwnProperty.call(object, property) : false; + } + + /** + * Creates an object composed of the inverted keys and values of the given `object`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to invert. + * @returns {Object} Returns the created inverted object. + * @example + * + * _.invert({ 'first': 'moe', 'second': 'larry' }); + * // => { 'moe': 'first', 'larry': 'second' } (order is not guaranteed) + */ + function invert(object) { + var index = -1, + props = keys(object), + length = props.length, + result = {}; + + while (++index < length) { + var key = props[index]; + result[object[key]] = key; + } + return result; + } + + /** + * Checks if `value` is a boolean value. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a boolean value, else `false`. + * @example + * + * _.isBoolean(null); + * // => false + */ + function isBoolean(value) { + return value === true || value === false || toString.call(value) == boolClass; + } + + /** + * Checks if `value` is a date. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a date, else `false`. + * @example + * + * _.isDate(new Date); + * // => true + */ + function isDate(value) { + return value instanceof Date || toString.call(value) == dateClass; + } + + /** + * Checks if `value` is a DOM element. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a DOM element, else `false`. + * @example + * + * _.isElement(document.body); + * // => true + */ + function isElement(value) { + return value ? value.nodeType === 1 : false; + } + + /** + * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a + * length of `0` and objects with no own enumerable properties are considered + * "empty". + * + * @static + * @memberOf _ + * @category Objects + * @param {Array|Object|String} value The value to inspect. + * @returns {Boolean} Returns `true`, if the `value` is empty, else `false`. + * @example + * + * _.isEmpty([1, 2, 3]); + * // => false + * + * _.isEmpty({}); + * // => true + * + * _.isEmpty(''); + * // => true + */ + function isEmpty(value) { + var result = true; + if (!value) { + return result; + } + var className = toString.call(value), + length = value.length; + + if ((className == arrayClass || className == stringClass || + className == argsClass || (noArgsClass && isArguments(value))) || + (className == objectClass && typeof length == 'number' && isFunction(value.splice))) { + return !length; + } + forOwn(value, function() { + return (result = false); + }); + return result; + } + + /** + * Performs a deep comparison between two values to determine if they are + * equivalent to each other. If `callback` is passed, it will be executed to + * compare values. If `callback` returns `undefined`, comparisons will be handled + * by the method instead. The `callback` is bound to `thisArg` and invoked with + * two arguments; (a, b). + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} a The value to compare. + * @param {Mixed} b The other value to compare. + * @param {Function} [callback] The function to customize comparing values. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param- {Object} [stackA=[]] Internally used track traversed `a` objects. + * @param- {Object} [stackB=[]] Internally used track traversed `b` objects. + * @returns {Boolean} Returns `true`, if the values are equvalent, else `false`. + * @example + * + * var moe = { 'name': 'moe', 'age': 40 }; + * var copy = { 'name': 'moe', 'age': 40 }; + * + * moe == copy; + * // => false + * + * _.isEqual(moe, copy); + * // => true + * + * var words = ['hello', 'goodbye']; + * var otherWords = ['hi', 'goodbye']; + * + * _.isEqual(words, otherWords, function(a, b) { + * var reGreet = /^(?:hello|hi)$/i, + * aGreet = _.isString(a) && reGreet.test(a), + * bGreet = _.isString(b) && reGreet.test(b); + * + * return (aGreet || bGreet) ? (aGreet == bGreet) : undefined; + * }); + * // => true + */ + function isEqual(a, b, callback, thisArg, stackA, stackB) { + // used to indicate that when comparing objects, `a` has at least the properties of `b` + var whereIndicator = callback === indicatorObject; + if (callback && !whereIndicator) { + callback = typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg, 2); + var result = callback(a, b); + if (typeof result != 'undefined') { + return !!result; + } + } + // exit early for identical values + if (a === b) { + // treat `+0` vs. `-0` as not equal + return a !== 0 || (1 / a == 1 / b); + } + var type = typeof a, + otherType = typeof b; + + // exit early for unlike primitive values + if (a === a && + (!a || (type != 'function' && type != 'object')) && + (!b || (otherType != 'function' && otherType != 'object'))) { + return false; + } + // exit early for `null` and `undefined`, avoiding ES3's Function#call behavior + // http://es5.github.com/#x15.3.4.4 + if (a == null || b == null) { + return a === b; + } + // compare [[Class]] names + var className = toString.call(a), + otherClass = toString.call(b); + + if (className == argsClass) { + className = objectClass; + } + if (otherClass == argsClass) { + otherClass = objectClass; + } + if (className != otherClass) { + return false; + } + switch (className) { + case boolClass: + case dateClass: + // coerce dates and booleans to numbers, dates to milliseconds and booleans + // to `1` or `0`, treating invalid dates coerced to `NaN` as not equal + return +a == +b; + + case numberClass: + // treat `NaN` vs. `NaN` as equal + return a != +a + ? b != +b + // but treat `+0` vs. `-0` as not equal + : (a == 0 ? (1 / a == 1 / b) : a == +b); + + case regexpClass: + case stringClass: + // coerce regexes to strings (http://es5.github.com/#x15.10.6.4) + // treat string primitives and their corresponding object instances as equal + return a == b + ''; + } + var isArr = className == arrayClass; + if (!isArr) { + // unwrap any `lodash` wrapped values + if (a.__wrapped__ || b.__wrapped__) { + return isEqual(a.__wrapped__ || a, b.__wrapped__ || b, callback, thisArg, stackA, stackB); + } + // exit for functions and DOM nodes + if (className != objectClass || (noNodeClass && (isNode(a) || isNode(b)))) { + return false; + } + // in older versions of Opera, `arguments` objects have `Array` constructors + var ctorA = !argsAreObjects && isArguments(a) ? Object : a.constructor, + ctorB = !argsAreObjects && isArguments(b) ? Object : b.constructor; + + // non `Object` object instances with different constructors are not equal + if (ctorA != ctorB && !( + isFunction(ctorA) && ctorA instanceof ctorA && + isFunction(ctorB) && ctorB instanceof ctorB + )) { + return false; + } + } + // assume cyclic structures are equal + // the algorithm for detecting cyclic structures is adapted from ES 5.1 + // section 15.12.3, abstract operation `JO` (http://es5.github.com/#x15.12.3) + stackA || (stackA = []); + stackB || (stackB = []); + + var length = stackA.length; + while (length--) { + if (stackA[length] == a) { + return stackB[length] == b; + } + } + var size = 0; + result = true; + + // add `a` and `b` to the stack of traversed objects + stackA.push(a); + stackB.push(b); + + // recursively compare objects and arrays (susceptible to call stack limits) + if (isArr) { + // compare lengths to determine if a deep comparison is necessary + size = b.length; + result = whereIndicator || size == a.length; + + if (result) { + // deep compare the contents, ignoring non-numeric properties + while (size--) { + if (!(result = isEqual(a[size], b[size], callback, thisArg, stackA, stackB))) { + break; + } + } + } + return result; + } + // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys` + // which, in this case, is more costly + forIn(b, function(value, key, b) { + if (hasOwnProperty.call(b, key)) { + // count the number of properties. + size++; + // deep compare each property value. + return (result = hasOwnProperty.call(a, key) && isEqual(a[key], value, callback, thisArg, stackA, stackB)); + } + }); + + if (result && !whereIndicator) { + // ensure both objects have the same number of properties + forIn(a, function(value, key, a) { + if (hasOwnProperty.call(a, key)) { + // `size` will be `-1` if `a` has more properties than `b` + return (result = --size > -1); + } + }); + } + return result; + } + + /** + * Checks if `value` is, or can be coerced to, a finite number. + * + * Note: This is not the same as native `isFinite`, which will return true for + * booleans and empty strings. See http://es5.github.com/#x15.1.2.5. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is finite, else `false`. + * @example + * + * _.isFinite(-101); + * // => true + * + * _.isFinite('10'); + * // => true + * + * _.isFinite(true); + * // => false + * + * _.isFinite(''); + * // => false + * + * _.isFinite(Infinity); + * // => false + */ + function isFinite(value) { + return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value)); + } + + /** + * Checks if `value` is a function. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + */ + function isFunction(value) { + return typeof value == 'function'; + } + // fallback for older versions of Chrome and Safari + if (isFunction(/x/)) { + isFunction = function(value) { + return value instanceof Function || toString.call(value) == funcClass; + }; + } + + /** + * Checks if `value` is the language type of Object. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ + function isObject(value) { + // check if the value is the ECMAScript language type of Object + // http://es5.github.com/#x8 + // and avoid a V8 bug + // http://code.google.com/p/v8/issues/detail?id=2291 + return value ? objectTypes[typeof value] : false; + } + + /** + * Checks if `value` is `NaN`. + * + * Note: This is not the same as native `isNaN`, which will return `true` for + * `undefined` and other values. See http://es5.github.com/#x15.1.2.4. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is `NaN`, else `false`. + * @example + * + * _.isNaN(NaN); + * // => true + * + * _.isNaN(new Number(NaN)); + * // => true + * + * isNaN(undefined); + * // => true + * + * _.isNaN(undefined); + * // => false + */ + function isNaN(value) { + // `NaN` as a primitive is the only value that is not equal to itself + // (perform the [[Class]] check first to avoid errors with some host objects in IE) + return isNumber(value) && value != +value + } + + /** + * Checks if `value` is `null`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is `null`, else `false`. + * @example + * + * _.isNull(null); + * // => true + * + * _.isNull(undefined); + * // => false + */ + function isNull(value) { + return value === null; + } + + /** + * Checks if `value` is a number. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a number, else `false`. + * @example + * + * _.isNumber(8.4 * 5); + * // => true + */ + function isNumber(value) { + return typeof value == 'number' || toString.call(value) == numberClass; + } + + /** + * Checks if a given `value` is an object created by the `Object` constructor. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if `value` is a plain object, else `false`. + * @example + * + * function Stooge(name, age) { + * this.name = name; + * this.age = age; + * } + * + * _.isPlainObject(new Stooge('moe', 40)); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'name': 'moe', 'age': 40 }); + * // => true + */ + var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) { + if (!(value && typeof value == 'object')) { + return false; + } + var valueOf = value.valueOf, + objProto = typeof valueOf == 'function' && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto); + + return objProto + ? value == objProto || (getPrototypeOf(value) == objProto && !isArguments(value)) + : shimIsPlainObject(value); + }; + + /** + * Checks if `value` is a regular expression. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a regular expression, else `false`. + * @example + * + * _.isRegExp(/moe/); + * // => true + */ + function isRegExp(value) { + return value instanceof RegExp || toString.call(value) == regexpClass; + } + + /** + * Checks if `value` is a string. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a string, else `false`. + * @example + * + * _.isString('moe'); + * // => true + */ + function isString(value) { + return typeof value == 'string' || toString.call(value) == stringClass; + } + + /** + * Checks if `value` is `undefined`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is `undefined`, else `false`. + * @example + * + * _.isUndefined(void 0); + * // => true + */ + function isUndefined(value) { + return typeof value == 'undefined'; + } + + /** + * Recursively merges own enumerable properties of the source object(s), that + * don't resolve to `undefined`, into the destination object. Subsequent sources + * will overwrite propery assignments of previous sources. If a `callback` function + * is passed, it will be executed to produce the merged values of the destination + * and source properties. If `callback` returns `undefined`, merging will be + * handled by the method instead. The `callback` is bound to `thisArg` and + * invoked with two arguments; (objectValue, sourceValue). + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @param {Function} [callback] The function to customize merging properties. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param- {Object} [deepIndicator] Internally used to indicate that `stackA` + * and `stackB` are arrays of traversed objects instead of source objects. + * @param- {Array} [stackA=[]] Internally used to track traversed source objects. + * @param- {Array} [stackB=[]] Internally used to associate values with their + * source counterparts. + * @returns {Object} Returns the destination object. + * @example + * + * var names = { + * 'stooges': [ + * { 'name': 'moe' }, + * { 'name': 'larry' } + * ] + * }; + * + * var ages = { + * 'stooges': [ + * { 'age': 40 }, + * { 'age': 50 } + * ] + * }; + * + * _.merge(names, ages); + * // => { 'stooges': [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] } + * + * var food = { + * 'fruits': ['apple'], + * 'vegetables': ['beet'] + * }; + * + * var otherFood = { + * 'fruits': ['banana'], + * 'vegetables': ['carrot'] + * }; + * + * _.merge(food, otherFood, function(a, b) { + * return _.isArray(a) ? a.concat(b) : undefined; + * }); + * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] } + */ + function merge(object, source, deepIndicator) { + var args = arguments, + index = 0, + length = 2; + + if (!isObject(object)) { + return object; + } + if (deepIndicator === indicatorObject) { + var callback = args[3], + stackA = args[4], + stackB = args[5]; + } else { + stackA = []; + stackB = []; + + // allows working with `_.reduce` and `_.reduceRight` without + // using their `callback` arguments, `index|key` and `collection` + if (typeof deepIndicator != 'number') { + length = args.length; + } + if (length > 2) { + if (typeof args[length - 2] == 'function') { + callback = createCallback(args[--length - 1], args[length--], 2); + } else if (typeof args[length - 1] == 'function') { + callback = args[--length]; + } + } + } + while (++index < length) { + (isArray(args[index]) ? forEach : forOwn)(args[index], function(source, key) { + var found, + isArr, + result = source, + value = object[key]; + + if (source && ((isArr = isArray(source)) || isPlainObject(source))) { + // avoid merging previously merged cyclic sources + var stackLength = stackA.length; + while (stackLength--) { + if ((found = stackA[stackLength] == source)) { + value = stackB[stackLength]; + break; + } + } + if (!found) { + value = isArr + ? (isArray(value) ? value : []) + : (isPlainObject(value) ? value : {}); + + if (callback) { + result = callback(value, source); + if (typeof result != 'undefined') { + value = result; + } + } + // add `source` and associated `value` to the stack of traversed objects + stackA.push(source); + stackB.push(value); + + // recursively merge objects and arrays (susceptible to call stack limits) + if (!callback) { + value = merge(value, source, indicatorObject, callback, stackA, stackB); + } + } + } + else { + if (callback) { + result = callback(value, source); + if (typeof result == 'undefined') { + result = source; + } + } + if (typeof result != 'undefined') { + value = result; + } + } + object[key] = value; + }); + } + return object; + } + + /** + * Creates a shallow clone of `object` excluding the specified properties. + * Property names may be specified as individual arguments or as arrays of + * property names. If a `callback` function is passed, it will be executed + * for each property in the `object`, omitting the properties `callback` + * returns truthy for. The `callback` is bound to `thisArg` and invoked + * with three arguments; (value, key, object). + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The source object. + * @param {Function|String} callback|[prop1, prop2, ...] The properties to omit + * or the function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns an object without the omitted properties. + * @example + * + * _.omit({ 'name': 'moe', 'age': 40 }, 'age'); + * // => { 'name': 'moe' } + * + * _.omit({ 'name': 'moe', 'age': 40 }, function(value) { + * return typeof value == 'number'; + * }); + * // => { 'name': 'moe' } + */ + function omit(object, callback, thisArg) { + var isFunc = typeof callback == 'function', + result = {}; + + if (isFunc) { + callback = createCallback(callback, thisArg); + } else { + var props = concat.apply(arrayRef, arguments); + } + forIn(object, function(value, key, object) { + if (isFunc + ? !callback(value, key, object) + : indexOf(props, key, 1) < 0 + ) { + result[key] = value; + } + }); + return result; + } + + /** + * Creates a two dimensional array of the given object's key-value pairs, + * i.e. `[[key1, value1], [key2, value2]]`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns new array of key-value pairs. + * @example + * + * _.pairs({ 'moe': 30, 'larry': 40 }); + * // => [['moe', 30], ['larry', 40]] (order is not guaranteed) + */ + function pairs(object) { + var index = -1, + props = keys(object), + length = props.length, + result = Array(length); + + while (++index < length) { + var key = props[index]; + result[index] = [key, object[key]]; + } + return result; + } + + /** + * Creates a shallow clone of `object` composed of the specified properties. + * Property names may be specified as individual arguments or as arrays of property + * names. If `callback` is passed, it will be executed for each property in the + * `object`, picking the properties `callback` returns truthy for. The `callback` + * is bound to `thisArg` and invoked with three arguments; (value, key, object). + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The source object. + * @param {Array|Function|String} callback|[prop1, prop2, ...] The function called + * per iteration or properties to pick, either as individual arguments or arrays. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns an object composed of the picked properties. + * @example + * + * _.pick({ 'name': 'moe', '_userid': 'moe1' }, 'name'); + * // => { 'name': 'moe' } + * + * _.pick({ 'name': 'moe', '_userid': 'moe1' }, function(value, key) { + * return key.charAt(0) != '_'; + * }); + * // => { 'name': 'moe' } + */ + function pick(object, callback, thisArg) { + var result = {}; + if (typeof callback != 'function') { + var index = 0, + props = concat.apply(arrayRef, arguments), + length = isObject(object) ? props.length : 0; + + while (++index < length) { + var key = props[index]; + if (key in object) { + result[key] = object[key]; + } + } + } else { + callback = createCallback(callback, thisArg); + forIn(object, function(value, key, object) { + if (callback(value, key, object)) { + result[key] = value; + } + }); + } + return result; + } + + /** + * Creates an array composed of the own enumerable property values of `object`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property values. + * @example + * + * _.values({ 'one': 1, 'two': 2, 'three': 3 }); + * // => [1, 2, 3] + */ + function values(object) { + var index = -1, + props = keys(object), + length = props.length, + result = Array(length); + + while (++index < length) { + result[index] = object[props[index]]; + } + return result; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Creates an array of elements from the specified index(es), or keys, of the + * `collection`. Indexes may be specified as individual arguments or as arrays + * of indexes. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Array|Number|String} [index1, index2, ...] The index(es) of + * `collection` to retrieve, either as individual arguments or arrays. + * @returns {Array} Returns a new array of elements corresponding to the + * provided indexes. + * @example + * + * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]); + * // => ['a', 'c', 'e'] + * + * _.at(['moe', 'larry', 'curly'], 0, 2); + * // => ['moe', 'curly'] + */ + function at(collection) { + var index = -1, + props = concat.apply(arrayRef, slice(arguments, 1)), + length = props.length, + result = Array(length); + + if (noCharByIndex && isString(collection)) { + collection = collection.split(''); + } + while(++index < length) { + result[index] = collection[props[index]]; + } + return result; + } + + /** + * Checks if a given `target` element is present in a `collection` using strict + * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used + * as the offset from the end of the collection. + * + * @static + * @memberOf _ + * @alias include + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Mixed} target The value to check for. + * @param {Number} [fromIndex=0] The index to search from. + * @returns {Boolean} Returns `true` if the `target` element is found, else `false`. + * @example + * + * _.contains([1, 2, 3], 1); + * // => true + * + * _.contains([1, 2, 3], 1, 2); + * // => false + * + * _.contains({ 'name': 'moe', 'age': 40 }, 'moe'); + * // => true + * + * _.contains('curly', 'ur'); + * // => true + */ + function contains(collection, target, fromIndex) { + var index = -1, + length = collection ? collection.length : 0, + result = false; + + fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0; + if (typeof length == 'number') { + result = (isString(collection) + ? collection.indexOf(target, fromIndex) + : indexOf(collection, target, fromIndex) + ) > -1; + } else { + each(collection, function(value) { + if (++index >= fromIndex) { + return !(result = value === target); + } + }); + } + return result; + } + + /** + * Creates an object composed of keys returned from running each element of the + * `collection` through the given `callback`. The corresponding value of each key + * is the number of times the key was returned by the `callback`. The `callback` + * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); }); + * // => { '4': 1, '6': 2 } + * + * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math); + * // => { '4': 1, '6': 2 } + * + * _.countBy(['one', 'two', 'three'], 'length'); + * // => { '3': 2, '5': 1 } + */ + function countBy(collection, callback, thisArg) { + var result = {}; + callback = createCallback(callback, thisArg); + + forEach(collection, function(value, key, collection) { + key = callback(value, key, collection) + ''; + (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1); + }); + return result; + } + + /** + * Checks if the `callback` returns a truthy value for **all** elements of a + * `collection`. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias all + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Boolean} Returns `true` if all elements pass the callback check, + * else `false`. + * @example + * + * _.every([true, 1, null, 'yes'], Boolean); + * // => false + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * // using "_.pluck" callback shorthand + * _.every(stooges, 'age'); + * // => true + * + * // using "_.where" callback shorthand + * _.every(stooges, { 'age': 50 }); + * // => false + */ + function every(collection, callback, thisArg) { + var result = true; + callback = createCallback(callback, thisArg); + + if (isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + if (!(result = !!callback(collection[index], index, collection))) { + break; + } + } + } else { + each(collection, function(value, index, collection) { + return (result = !!callback(value, index, collection)); + }); + } + return result; + } + + /** + * Examines each element in a `collection`, returning an array of all elements + * the `callback` returns truthy for. The `callback` is bound to `thisArg` and + * invoked with three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias select + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of elements that passed the callback check. + * @example + * + * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [2, 4, 6] + * + * var food = [ + * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, + * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.filter(food, 'organic'); + * // => [{ 'name': 'carrot', 'organic': true, 'type': 'vegetable' }] + * + * // using "_.where" callback shorthand + * _.filter(food, { 'type': 'fruit' }); + * // => [{ 'name': 'apple', 'organic': false, 'type': 'fruit' }] + */ + function filter(collection, callback, thisArg) { + var result = []; + callback = createCallback(callback, thisArg); + + if (isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + var value = collection[index]; + if (callback(value, index, collection)) { + result.push(value); + } + } + } else { + each(collection, function(value, index, collection) { + if (callback(value, index, collection)) { + result.push(value); + } + }); + } + return result; + } + + /** + * Examines each element in a `collection`, returning the first that the `callback` + * returns truthy for. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias detect + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the element that passed the callback check, + * else `undefined`. + * @example + * + * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => 2 + * + * var food = [ + * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, + * { 'name': 'banana', 'organic': true, 'type': 'fruit' }, + * { 'name': 'beet', 'organic': false, 'type': 'vegetable' }, + * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * var veggie = _.find(food, { 'type': 'vegetable' }); + * // => { 'name': 'beet', 'organic': false, 'type': 'vegetable' } + * + * // using "_.pluck" callback shorthand + * var healthy = _.find(food, 'organic'); + * // => { 'name': 'banana', 'organic': true, 'type': 'fruit' } + */ + function find(collection, callback, thisArg) { + var result; + callback = createCallback(callback, thisArg); + + forEach(collection, function(value, index, collection) { + if (callback(value, index, collection)) { + result = value; + return false; + } + }); + return result; + } + + /** + * Iterates over a `collection`, executing the `callback` for each element in + * the `collection`. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). Callbacks may exit iteration early + * by explicitly returning `false`. + * + * @static + * @memberOf _ + * @alias each + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array|Object|String} Returns `collection`. + * @example + * + * _([1, 2, 3]).forEach(alert).join(','); + * // => alerts each number and returns '1,2,3' + * + * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert); + * // => alerts each number value (order is not guaranteed) + */ + function forEach(collection, callback, thisArg) { + if (callback && typeof thisArg == 'undefined' && isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + if (callback(collection[index], index, collection) === false) { + break; + } + } + } else { + each(collection, callback, thisArg); + } + return collection; + } + + /** + * Creates an object composed of keys returned from running each element of the + * `collection` through the `callback`. The corresponding value of each key is + * an array of elements passed to `callback` that returned the key. The `callback` + * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false` + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); }); + * // => { '4': [4.2], '6': [6.1, 6.4] } + * + * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math); + * // => { '4': [4.2], '6': [6.1, 6.4] } + * + * // using "_.pluck" callback shorthand + * _.groupBy(['one', 'two', 'three'], 'length'); + * // => { '3': ['one', 'two'], '5': ['three'] } + */ + function groupBy(collection, callback, thisArg) { + var result = {}; + callback = createCallback(callback, thisArg); + + forEach(collection, function(value, key, collection) { + key = callback(value, key, collection) + ''; + (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value); + }); + return result; + } + + /** + * Invokes the method named by `methodName` on each element in the `collection`, + * returning an array of the results of each invoked method. Additional arguments + * will be passed to each invoked method. If `methodName` is a function, it will + * be invoked for, and `this` bound to, each element in the `collection`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|String} methodName The name of the method to invoke or + * the function invoked per iteration. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. + * @returns {Array} Returns a new array of the results of each invoked method. + * @example + * + * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); + * // => [[1, 5, 7], [1, 2, 3]] + * + * _.invoke([123, 456], String.prototype.split, ''); + * // => [['1', '2', '3'], ['4', '5', '6']] + */ + function invoke(collection, methodName) { + var args = slice(arguments, 2), + index = -1, + isFunc = typeof methodName == 'function', + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); + + forEach(collection, function(value) { + result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args); + }); + return result; + } + + /** + * Creates an array of values by running each element in the `collection` + * through the `callback`. The `callback` is bound to `thisArg` and invoked with + * three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias collect + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of the results of each `callback` execution. + * @example + * + * _.map([1, 2, 3], function(num) { return num * 3; }); + * // => [3, 6, 9] + * + * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; }); + * // => [3, 6, 9] (order is not guaranteed) + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * // using "_.pluck" callback shorthand + * _.map(stooges, 'name'); + * // => ['moe', 'larry'] + */ + function map(collection, callback, thisArg) { + var index = -1, + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); + + callback = createCallback(callback, thisArg); + if (isArray(collection)) { + while (++index < length) { + result[index] = callback(collection[index], index, collection); + } + } else { + each(collection, function(value, key, collection) { + result[++index] = callback(value, key, collection); + }); + } + return result; + } + + /** + * Retrieves the maximum value of an `array`. If `callback` is passed, + * it will be executed for each value in the `array` to generate the + * criterion by which the value is ranked. The `callback` is bound to + * `thisArg` and invoked with three arguments; (value, index, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the maximum value. + * @example + * + * _.max([4, 2, 8, 6]); + * // => 8 + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * _.max(stooges, function(stooge) { return stooge.age; }); + * // => { 'name': 'larry', 'age': 50 }; + * + * // using "_.pluck" callback shorthand + * _.max(stooges, 'age'); + * // => { 'name': 'larry', 'age': 50 }; + */ + function max(collection, callback, thisArg) { + var computed = -Infinity, + result = computed; + + if (!callback && isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + var value = collection[index]; + if (value > result) { + result = value; + } + } + } else { + callback = !callback && isString(collection) + ? charAtCallback + : createCallback(callback, thisArg); + + each(collection, function(value, index, collection) { + var current = callback(value, index, collection); + if (current > computed) { + computed = current; + result = value; + } + }); + } + return result; + } + + /** + * Retrieves the minimum value of an `array`. If `callback` is passed, + * it will be executed for each value in the `array` to generate the + * criterion by which the value is ranked. The `callback` is bound to `thisArg` + * and invoked with three arguments; (value, index, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the minimum value. + * @example + * + * _.min([4, 2, 8, 6]); + * // => 2 + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * _.min(stooges, function(stooge) { return stooge.age; }); + * // => { 'name': 'moe', 'age': 40 }; + * + * // using "_.pluck" callback shorthand + * _.min(stooges, 'age'); + * // => { 'name': 'moe', 'age': 40 }; + */ + function min(collection, callback, thisArg) { + var computed = Infinity, + result = computed; + + if (!callback && isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + var value = collection[index]; + if (value < result) { + result = value; + } + } + } else { + callback = !callback && isString(collection) + ? charAtCallback + : createCallback(callback, thisArg); + + each(collection, function(value, index, collection) { + var current = callback(value, index, collection); + if (current < computed) { + computed = current; + result = value; + } + }); + } + return result; + } + + /** + * Retrieves the value of a specified property from all elements in the `collection`. + * + * @static + * @memberOf _ + * @type Function + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {String} property The property to pluck. + * @returns {Array} Returns a new array of property values. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * _.pluck(stooges, 'name'); + * // => ['moe', 'larry'] + */ + var pluck = map; + + /** + * Reduces a `collection` to a value that is the accumulated result of running + * each element in the `collection` through the `callback`, where each successive + * `callback` execution consumes the return value of the previous execution. + * If `accumulator` is not passed, the first element of the `collection` will be + * used as the initial `accumulator` value. The `callback` is bound to `thisArg` + * and invoked with four arguments; (accumulator, value, index|key, collection). + * + * @static + * @memberOf _ + * @alias foldl, inject + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [accumulator] Initial value of the accumulator. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the accumulated value. + * @example + * + * var sum = _.reduce([1, 2, 3], function(sum, num) { + * return sum + num; + * }); + * // => 6 + * + * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) { + * result[key] = num * 3; + * return result; + * }, {}); + * // => { 'a': 3, 'b': 6, 'c': 9 } + */ + function reduce(collection, callback, accumulator, thisArg) { + var noaccum = arguments.length < 3; + callback = createCallback(callback, thisArg, 4); + + if (isArray(collection)) { + var index = -1, + length = collection.length; + + if (noaccum) { + accumulator = collection[++index]; + } + while (++index < length) { + accumulator = callback(accumulator, collection[index], index, collection); + } + } else { + each(collection, function(value, index, collection) { + accumulator = noaccum + ? (noaccum = false, value) + : callback(accumulator, value, index, collection) + }); + } + return accumulator; + } + + /** + * This method is similar to `_.reduce`, except that it iterates over a + * `collection` from right to left. + * + * @static + * @memberOf _ + * @alias foldr + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [accumulator] Initial value of the accumulator. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the accumulated value. + * @example + * + * var list = [[0, 1], [2, 3], [4, 5]]; + * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []); + * // => [4, 5, 2, 3, 0, 1] + */ + function reduceRight(collection, callback, accumulator, thisArg) { + var iterable = collection, + length = collection ? collection.length : 0, + noaccum = arguments.length < 3; + + if (typeof length != 'number') { + var props = keys(collection); + length = props.length; + } else if (noCharByIndex && isString(collection)) { + iterable = collection.split(''); + } + callback = createCallback(callback, thisArg, 4); + forEach(collection, function(value, index, collection) { + index = props ? props[--length] : --length; + accumulator = noaccum + ? (noaccum = false, iterable[index]) + : callback(accumulator, iterable[index], index, collection); + }); + return accumulator; + } + + /** + * The opposite of `_.filter`, this method returns the elements of a + * `collection` that `callback` does **not** return truthy for. + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of elements that did **not** pass the + * callback check. + * @example + * + * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [1, 3, 5] + * + * var food = [ + * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, + * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.reject(food, 'organic'); + * // => [{ 'name': 'apple', 'organic': false, 'type': 'fruit' }] + * + * // using "_.where" callback shorthand + * _.reject(food, { 'type': 'fruit' }); + * // => [{ 'name': 'carrot', 'organic': true, 'type': 'vegetable' }] + */ + function reject(collection, callback, thisArg) { + callback = createCallback(callback, thisArg); + return filter(collection, function(value, index, collection) { + return !callback(value, index, collection); + }); + } + + /** + * Creates an array of shuffled `array` values, using a version of the + * Fisher-Yates shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to shuffle. + * @returns {Array} Returns a new shuffled collection. + * @example + * + * _.shuffle([1, 2, 3, 4, 5, 6]); + * // => [4, 1, 6, 3, 5, 2] + */ + function shuffle(collection) { + var index = -1, + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); + + forEach(collection, function(value) { + var rand = floor(nativeRandom() * (++index + 1)); + result[index] = result[rand]; + result[rand] = value; + }); + return result; + } + + /** + * Gets the size of the `collection` by returning `collection.length` for arrays + * and array-like objects or the number of own enumerable properties for objects. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to inspect. + * @returns {Number} Returns `collection.length` or number of own enumerable properties. + * @example + * + * _.size([1, 2]); + * // => 2 + * + * _.size({ 'one': 1, 'two': 2, 'three': 3 }); + * // => 3 + * + * _.size('curly'); + * // => 5 + */ + function size(collection) { + var length = collection ? collection.length : 0; + return typeof length == 'number' ? length : keys(collection).length; + } + + /** + * Checks if the `callback` returns a truthy value for **any** element of a + * `collection`. The function returns as soon as it finds passing value, and + * does not iterate over the entire `collection`. The `callback` is bound to + * `thisArg` and invoked with three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias any + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Boolean} Returns `true` if any element passes the callback check, + * else `false`. + * @example + * + * _.some([null, 0, 'yes', false], Boolean); + * // => true + * + * var food = [ + * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, + * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.some(food, 'organic'); + * // => true + * + * // using "_.where" callback shorthand + * _.some(food, { 'type': 'meat' }); + * // => false + */ + function some(collection, callback, thisArg) { + var result; + callback = createCallback(callback, thisArg); + + if (isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + if ((result = callback(collection[index], index, collection))) { + break; + } + } + } else { + each(collection, function(value, index, collection) { + return !(result = callback(value, index, collection)); + }); + } + return !!result; + } + + /** + * Creates an array of elements, sorted in ascending order by the results of + * running each element in the `collection` through the `callback`. This method + * performs a stable sort, that is, it will preserve the original sort order of + * equal elements. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of sorted elements. + * @example + * + * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); }); + * // => [3, 1, 2] + * + * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math); + * // => [3, 1, 2] + * + * // using "_.pluck" callback shorthand + * _.sortBy(['banana', 'strawberry', 'apple'], 'length'); + * // => ['apple', 'banana', 'strawberry'] + */ + function sortBy(collection, callback, thisArg) { + var index = -1, + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); + + callback = createCallback(callback, thisArg); + forEach(collection, function(value, key, collection) { + result[++index] = { + 'criteria': callback(value, key, collection), + 'index': index, + 'value': value + }; + }); + + length = result.length; + result.sort(compareAscending); + while (length--) { + result[length] = result[length].value; + } + return result; + } + + /** + * Converts the `collection` to an array. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to convert. + * @returns {Array} Returns the new converted array. + * @example + * + * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4); + * // => [2, 3, 4] + */ + function toArray(collection) { + if (collection && typeof collection.length == 'number') { + return noCharByIndex && isString(collection) + ? collection.split('') + : slice(collection); + } + return values(collection); + } + + /** + * Examines each element in a `collection`, returning an array of all elements + * that have the given `properties`. When checking `properties`, this method + * performs a deep comparison between values to determine if they are equivalent + * to each other. + * + * @static + * @memberOf _ + * @type Function + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Object} properties The object of property values to filter by. + * @returns {Array} Returns a new array of elements that have the given `properties`. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * _.where(stooges, { 'age': 40 }); + * // => [{ 'name': 'moe', 'age': 40 }] + */ + var where = filter; + + /*--------------------------------------------------------------------------*/ + + /** + * Creates an array with all falsey values of `array` removed. The values + * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to compact. + * @returns {Array} Returns a new filtered array. + * @example + * + * _.compact([0, 1, false, 2, '', 3]); + * // => [1, 2, 3] + */ + function compact(array) { + var index = -1, + length = array ? array.length : 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (value) { + result.push(value); + } + } + return result; + } + + /** + * Creates an array of `array` elements not present in the other arrays + * using strict equality for comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to process. + * @param {Array} [array1, array2, ...] Arrays to check. + * @returns {Array} Returns a new array of `array` elements not present in the + * other arrays. + * @example + * + * _.difference([1, 2, 3, 4, 5], [5, 2, 10]); + * // => [1, 3, 4] + */ + function difference(array) { + var index = -1, + length = array ? array.length : 0, + flattened = concat.apply(arrayRef, arguments), + contains = cachedContains(flattened, length), + result = []; + + while (++index < length) { + var value = array[index]; + if (!contains(value)) { + result.push(value); + } + } + return result; + } + + /** + * Gets the first element of the `array`. If a number `n` is passed, the first + * `n` elements of the `array` are returned. If a `callback` function is passed, + * the first elements the `callback` returns truthy for are returned. The `callback` + * is bound to `thisArg` and invoked with three arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias head, take + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|Number|String} [callback|n] The function called + * per element or the number of elements to return. If a property name or + * object is passed, it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the first element(s) of `array`. + * @example + * + * _.first([1, 2, 3]); + * // => 1 + * + * _.first([1, 2, 3], 2); + * // => [1, 2] + * + * _.first([1, 2, 3], function(num) { + * return num < 3; + * }); + * // => [1, 2] + * + * var food = [ + * { 'name': 'banana', 'organic': true }, + * { 'name': 'beet', 'organic': false }, + * ]; + * + * // using "_.pluck" callback shorthand + * _.first(food, 'organic'); + * // => [{ 'name': 'banana', 'organic': true }] + * + * var food = [ + * { 'name': 'apple', 'type': 'fruit' }, + * { 'name': 'banana', 'type': 'fruit' }, + * { 'name': 'beet', 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.first(food, { 'type': 'fruit' }); + * // => [{ 'name': 'apple', 'type': 'fruit' }, { 'name': 'banana', 'type': 'fruit' }] + */ + function first(array, callback, thisArg) { + if (array) { + var n = 0, + length = array.length; + + if (typeof callback != 'number' && callback != null) { + var index = -1; + callback = createCallback(callback, thisArg); + while (++index < length && callback(array[index], index, array)) { + n++; + } + } else { + n = callback; + if (n == null || thisArg) { + return array[0]; + } + } + return slice(array, 0, nativeMin(nativeMax(0, n), length)); + } + } + + /** + * Flattens a nested array (the nesting can be to any depth). If `shallow` is + * truthy, `array` will only be flattened a single level. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to compact. + * @param {Boolean} shallow A flag to indicate only flattening a single level. + * @returns {Array} Returns a new flattened array. + * @example + * + * _.flatten([1, [2], [3, [[4]]]]); + * // => [1, 2, 3, 4]; + * + * _.flatten([1, [2], [3, [[4]]]], true); + * // => [1, 2, 3, [[4]]]; + */ + function flatten(array, shallow) { + var index = -1, + length = array ? array.length : 0, + result = []; + + while (++index < length) { + var value = array[index]; + + // recursively flatten arrays (susceptible to call stack limits) + if (isArray(value)) { + push.apply(result, shallow ? value : flatten(value)); + } else { + result.push(value); + } + } + return result; + } + + /** + * Gets the index at which the first occurrence of `value` is found using + * strict equality for comparisons, i.e. `===`. If the `array` is already + * sorted, passing `true` for `fromIndex` will run a faster binary search. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to search. + * @param {Mixed} value The value to search for. + * @param {Boolean|Number} [fromIndex=0] The index to search from or `true` to + * perform a binary search on a sorted `array`. + * @returns {Number} Returns the index of the matched value or `-1`. + * @example + * + * _.indexOf([1, 2, 3, 1, 2, 3], 2); + * // => 1 + * + * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3); + * // => 4 + * + * _.indexOf([1, 1, 2, 2, 3, 3], 2, true); + * // => 2 + */ + function indexOf(array, value, fromIndex) { + var index = -1, + length = array ? array.length : 0; + + if (typeof fromIndex == 'number') { + index = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0) - 1; + } else if (fromIndex) { + index = sortedIndex(array, value); + return array[index] === value ? index : -1; + } + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; + } + + /** + * Gets all but the last element of `array`. If a number `n` is passed, the + * last `n` elements are excluded from the result. If a `callback` function + * is passed, the last elements the `callback` returns truthy for are excluded + * from the result. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|Number|String} [callback|n=1] The function called + * per element or the number of elements to exclude. If a property name or + * object is passed, it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a slice of `array`. + * @example + * + * _.initial([1, 2, 3]); + * // => [1, 2] + * + * _.initial([1, 2, 3], 2); + * // => [1] + * + * _.initial([1, 2, 3], function(num) { + * return num > 1; + * }); + * // => [1] + * + * var food = [ + * { 'name': 'beet', 'organic': false }, + * { 'name': 'carrot', 'organic': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.initial(food, 'organic'); + * // => [{ 'name': 'beet', 'organic': false }] + * + * var food = [ + * { 'name': 'banana', 'type': 'fruit' }, + * { 'name': 'beet', 'type': 'vegetable' }, + * { 'name': 'carrot', 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.initial(food, { 'type': 'vegetable' }); + * // => [{ 'name': 'banana', 'type': 'fruit' }] + */ + function initial(array, callback, thisArg) { + if (!array) { + return []; + } + var n = 0, + length = array.length; + + if (typeof callback != 'number' && callback != null) { + var index = length; + callback = createCallback(callback, thisArg); + while (index-- && callback(array[index], index, array)) { + n++; + } + } else { + n = (callback == null || thisArg) ? 1 : callback || n; + } + return slice(array, 0, nativeMin(nativeMax(0, length - n), length)); + } + + /** + * Computes the intersection of all the passed-in arrays using strict equality + * for comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} [array1, array2, ...] Arrays to process. + * @returns {Array} Returns a new array of unique elements that are present + * in **all** of the arrays. + * @example + * + * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); + * // => [1, 2] + */ + function intersection(array) { + var args = arguments, + argsLength = args.length, + cache = { '0': {} }, + index = -1, + length = array ? array.length : 0, + isLarge = length >= 100, + result = [], + seen = result; + + outer: + while (++index < length) { + var value = array[index]; + if (isLarge) { + var key = value + ''; + var inited = hasOwnProperty.call(cache[0], key) + ? !(seen = cache[0][key]) + : (seen = cache[0][key] = []); + } + if (inited || indexOf(seen, value) < 0) { + if (isLarge) { + seen.push(value); + } + var argsIndex = argsLength; + while (--argsIndex) { + if (!(cache[argsIndex] || (cache[argsIndex] = cachedContains(args[argsIndex], 0, 100)))(value)) { + continue outer; + } + } + result.push(value); + } + } + return result; + } + + /** + * Gets the last element of the `array`. If a number `n` is passed, the last + * `n` elements of the `array` are returned. If a `callback` function is passed, + * the last elements the `callback` returns truthy for are returned. The `callback` + * is bound to `thisArg` and invoked with three arguments; (value, index, array). + * + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|Number|String} [callback|n] The function called + * per element or the number of elements to return. If a property name or + * object is passed, it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the last element(s) of `array`. + * @example + * + * _.last([1, 2, 3]); + * // => 3 + * + * _.last([1, 2, 3], 2); + * // => [2, 3] + * + * _.last([1, 2, 3], function(num) { + * return num > 1; + * }); + * // => [2, 3] + * + * var food = [ + * { 'name': 'beet', 'organic': false }, + * { 'name': 'carrot', 'organic': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.last(food, 'organic'); + * // => [{ 'name': 'carrot', 'organic': true }] + * + * var food = [ + * { 'name': 'banana', 'type': 'fruit' }, + * { 'name': 'beet', 'type': 'vegetable' }, + * { 'name': 'carrot', 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.last(food, { 'type': 'vegetable' }); + * // => [{ 'name': 'beet', 'type': 'vegetable' }, { 'name': 'carrot', 'type': 'vegetable' }] + */ + function last(array, callback, thisArg) { + if (array) { + var n = 0, + length = array.length; + + if (typeof callback != 'number' && callback != null) { + var index = length; + callback = createCallback(callback, thisArg); + while (index-- && callback(array[index], index, array)) { + n++; + } + } else { + n = callback; + if (n == null || thisArg) { + return array[length - 1]; + } + } + return slice(array, nativeMax(0, length - n)); + } + } + + /** + * Gets the index at which the last occurrence of `value` is found using strict + * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used + * as the offset from the end of the collection. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to search. + * @param {Mixed} value The value to search for. + * @param {Number} [fromIndex=array.length-1] The index to search from. + * @returns {Number} Returns the index of the matched value or `-1`. + * @example + * + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); + * // => 4 + * + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3); + * // => 1 + */ + function lastIndexOf(array, value, fromIndex) { + var index = array ? array.length : 0; + if (typeof fromIndex == 'number') { + index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1; + } + while (index--) { + if (array[index] === value) { + return index; + } + } + return -1; + } + + /** + * Creates an object composed from arrays of `keys` and `values`. Pass either + * a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`, or + * two arrays, one of `keys` and one of corresponding `values`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} keys The array of keys. + * @param {Array} [values=[]] The array of values. + * @returns {Object} Returns an object composed of the given keys and + * corresponding values. + * @example + * + * _.object(['moe', 'larry'], [30, 40]); + * // => { 'moe': 30, 'larry': 40 } + */ + function object(keys, values) { + var index = -1, + length = keys ? keys.length : 0, + result = {}; + + while (++index < length) { + var key = keys[index]; + if (values) { + result[key] = values[index]; + } else { + result[key[0]] = key[1]; + } + } + return result; + } + + /** + * Creates an array of numbers (positive and/or negative) progressing from + * `start` up to but not including `end`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Number} [start=0] The start of the range. + * @param {Number} end The end of the range. + * @param {Number} [step=1] The value to increment or descrement by. + * @returns {Array} Returns a new range array. + * @example + * + * _.range(10); + * // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + * + * _.range(1, 11); + * // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + * + * _.range(0, 30, 5); + * // => [0, 5, 10, 15, 20, 25] + * + * _.range(0, -10, -1); + * // => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] + * + * _.range(0); + * // => [] + */ + function range(start, end, step) { + start = +start || 0; + step = +step || 1; + + if (end == null) { + end = start; + start = 0; + } + // use `Array(length)` so V8 will avoid the slower "dictionary" mode + // http://youtu.be/XAqIpGU8ZZk#t=17m25s + var index = -1, + length = nativeMax(0, ceil((end - start) / step)), + result = Array(length); + + while (++index < length) { + result[index] = start; + start += step; + } + return result; + } + + /** + * The opposite of `_.initial`, this method gets all but the first value of `array`. + * If a number `n` is passed, the first `n` values are excluded from the result. + * If a `callback` function is passed, the first elements the `callback` returns + * truthy for are excluded from the result. The `callback` is bound to `thisArg` + * and invoked with three arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias drop, tail + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|Number|String} [callback|n=1] The function called + * per element or the number of elements to exclude. If a property name or + * object is passed, it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a slice of `array`. + * @example + * + * _.rest([1, 2, 3]); + * // => [2, 3] + * + * _.rest([1, 2, 3], 2); + * // => [3] + * + * _.rest([1, 2, 3], function(num) { + * return num < 3; + * }); + * // => [3] + * + * var food = [ + * { 'name': 'banana', 'organic': true }, + * { 'name': 'beet', 'organic': false }, + * ]; + * + * // using "_.pluck" callback shorthand + * _.rest(food, 'organic'); + * // => [{ 'name': 'beet', 'organic': false }] + * + * var food = [ + * { 'name': 'apple', 'type': 'fruit' }, + * { 'name': 'banana', 'type': 'fruit' }, + * { 'name': 'beet', 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.rest(food, { 'type': 'fruit' }); + * // => [{ 'name': 'beet', 'type': 'vegetable' }] + */ + function rest(array, callback, thisArg) { + if (typeof callback != 'number' && callback != null) { + var n = 0, + index = -1, + length = array ? array.length : 0; + + callback = createCallback(callback, thisArg); + while (++index < length && callback(array[index], index, array)) { + n++; + } + } else { + n = (callback == null || thisArg) ? 1 : nativeMax(0, callback); + } + return slice(array, n); + } + + /** + * Uses a binary search to determine the smallest index at which the `value` + * should be inserted into `array` in order to maintain the sort order of the + * sorted `array`. If `callback` is passed, it will be executed for `value` and + * each element in `array` to compute their sort ranking. The `callback` is + * bound to `thisArg` and invoked with one argument; (value). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to iterate over. + * @param {Mixed} value The value to evaluate. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Number} Returns the index at which the value should be inserted + * into `array`. + * @example + * + * _.sortedIndex([20, 30, 50], 40); + * // => 2 + * + * // using "_.pluck" callback shorthand + * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x'); + * // => 2 + * + * var dict = { + * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 } + * }; + * + * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { + * return dict.wordToNumber[word]; + * }); + * // => 2 + * + * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { + * return this.wordToNumber[word]; + * }, dict); + * // => 2 + */ + function sortedIndex(array, value, callback, thisArg) { + var low = 0, + high = array ? array.length : low; + + // explicitly reference `identity` for better inlining in Firefox + callback = callback ? createCallback(callback, thisArg, 1) : identity; + value = callback(value); + + while (low < high) { + var mid = (low + high) >>> 1; + callback(array[mid]) < value + ? low = mid + 1 + : high = mid; + } + return low; + } + + /** + * Computes the union of the passed-in arrays using strict equality for + * comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} [array1, array2, ...] Arrays to process. + * @returns {Array} Returns a new array of unique values, in order, that are + * present in one or more of the arrays. + * @example + * + * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); + * // => [1, 2, 3, 101, 10] + */ + function union() { + return uniq(concat.apply(arrayRef, arguments)); + } + + /** + * Creates a duplicate-value-free version of the `array` using strict equality + * for comparisons, i.e. `===`. If the `array` is already sorted, passing `true` + * for `isSorted` will run a faster algorithm. If `callback` is passed, each + * element of `array` is passed through a callback` before uniqueness is computed. + * The `callback` is bound to `thisArg` and invoked with three arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the propeties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias unique + * @category Arrays + * @param {Array} array The array to process. + * @param {Boolean} [isSorted=false] A flag to indicate that the `array` is already sorted. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a duplicate-value-free array. + * @example + * + * _.uniq([1, 2, 1, 3, 1]); + * // => [1, 2, 3] + * + * _.uniq([1, 1, 2, 2, 3], true); + * // => [1, 2, 3] + * + * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); }); + * // => [1, 2, 3] + * + * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math); + * // => [1, 2, 3] + * + * // using "_.pluck" callback shorthand + * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); + * // => [{ 'x': 1 }, { 'x': 2 }] + */ + function uniq(array, isSorted, callback, thisArg) { + var index = -1, + length = array ? array.length : 0, + result = [], + seen = result; + + // juggle arguments + if (typeof isSorted == 'function') { + thisArg = callback; + callback = isSorted; + isSorted = false; + } + // init value cache for large arrays + var isLarge = !isSorted && length >= 75; + if (isLarge) { + var cache = {}; + } + if (callback) { + seen = []; + callback = createCallback(callback, thisArg); + } + while (++index < length) { + var value = array[index], + computed = callback ? callback(value, index, array) : value; + + if (isLarge) { + var key = computed + ''; + var inited = hasOwnProperty.call(cache, key) + ? !(seen = cache[key]) + : (seen = cache[key] = []); + } + if (isSorted + ? !index || seen[seen.length - 1] !== computed + : inited || indexOf(seen, computed) < 0 + ) { + if (callback || isLarge) { + seen.push(computed); + } + result.push(value); + } + } + return result; + } + + /** + * Creates an array with all occurrences of the passed values removed using + * strict equality for comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to filter. + * @param {Mixed} [value1, value2, ...] Values to remove. + * @returns {Array} Returns a new filtered array. + * @example + * + * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); + * // => [2, 3, 4] + */ + function without(array) { + var index = -1, + length = array ? array.length : 0, + contains = cachedContains(arguments, 1), + result = []; + + while (++index < length) { + var value = array[index]; + if (!contains(value)) { + result.push(value); + } + } + return result; + } + + /** + * Groups the elements of each array at their corresponding indexes. Useful for + * separate data sources that are coordinated through matching array indexes. + * For a matrix of nested arrays, `_.zip.apply(...)` can transpose the matrix + * in a similar fashion. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} [array1, array2, ...] Arrays to process. + * @returns {Array} Returns a new array of grouped elements. + * @example + * + * _.zip(['moe', 'larry'], [30, 40], [true, false]); + * // => [['moe', 30, true], ['larry', 40, false]] + */ + function zip(array) { + var index = -1, + length = array ? max(pluck(arguments, 'length')) : 0, + result = Array(length); + + while (++index < length) { + result[index] = pluck(arguments, index); + } + return result; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Creates a function that is restricted to executing `func` only after it is + * called `n` times. The `func` is executed with the `this` binding of the + * created function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Number} n The number of times the function must be called before + * it is executed. + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * var renderNotes = _.after(notes.length, render); + * _.forEach(notes, function(note) { + * note.asyncSave({ 'success': renderNotes }); + * }); + * // `renderNotes` is run once, after all notes have saved + */ + function after(n, func) { + if (n < 1) { + return func(); + } + return function() { + if (--n < 1) { + return func.apply(this, arguments); + } + }; + } + + /** + * Creates a function that, when called, invokes `func` with the `this` + * binding of `thisArg` and prepends any additional `bind` arguments to those + * passed to the bound function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to bind. + * @param {Mixed} [thisArg] The `this` binding of `func`. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new bound function. + * @example + * + * var func = function(greeting) { + * return greeting + ' ' + this.name; + * }; + * + * func = _.bind(func, { 'name': 'moe' }, 'hi'); + * func(); + * // => 'hi moe' + */ + function bind(func, thisArg) { + // use `Function#bind` if it exists and is fast + // (in V8 `Function#bind` is slower except when partially applied) + return isBindFast || (nativeBind && arguments.length > 2) + ? nativeBind.call.apply(nativeBind, arguments) + : createBound(func, thisArg, slice(arguments, 2)); + } + + /** + * Binds methods on `object` to `object`, overwriting the existing method. + * Method names may be specified as individual arguments or as arrays of method + * names. If no method names are provided, all the function properties of `object` + * will be bound. + * + * @static + * @memberOf _ + * @category Functions + * @param {Object} object The object to bind and assign the bound methods to. + * @param {String} [methodName1, methodName2, ...] Method names on the object to bind. + * @returns {Object} Returns `object`. + * @example + * + * var view = { + * 'label': 'docs', + * 'onClick': function() { alert('clicked ' + this.label); } + * }; + * + * _.bindAll(view); + * jQuery('#docs').on('click', view.onClick); + * // => alerts 'clicked docs', when the button is clicked + */ + function bindAll(object) { + var funcs = concat.apply(arrayRef, arguments), + index = funcs.length > 1 ? 0 : (funcs = functions(object), -1), + length = funcs.length; + + while (++index < length) { + var key = funcs[index]; + object[key] = bind(object[key], object); + } + return object; + } + + /** + * Creates a function that, when called, invokes the method at `object[key]` + * and prepends any additional `bindKey` arguments to those passed to the bound + * function. This method differs from `_.bind` by allowing bound functions to + * reference methods that will be redefined or don't yet exist. + * See http://michaux.ca/articles/lazy-function-definition-pattern. + * + * @static + * @memberOf _ + * @category Functions + * @param {Object} object The object the method belongs to. + * @param {String} key The key of the method. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new bound function. + * @example + * + * var object = { + * 'name': 'moe', + * 'greet': function(greeting) { + * return greeting + ' ' + this.name; + * } + * }; + * + * var func = _.bindKey(object, 'greet', 'hi'); + * func(); + * // => 'hi moe' + * + * object.greet = function(greeting) { + * return greeting + ', ' + this.name + '!'; + * }; + * + * func(); + * // => 'hi, moe!' + */ + function bindKey(object, key) { + return createBound(object, key, slice(arguments, 2)); + } + + /** + * Creates a function that is the composition of the passed functions, + * where each function consumes the return value of the function that follows. + * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. + * Each function is executed with the `this` binding of the composed function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} [func1, func2, ...] Functions to compose. + * @returns {Function} Returns the new composed function. + * @example + * + * var greet = function(name) { return 'hi ' + name; }; + * var exclaim = function(statement) { return statement + '!'; }; + * var welcome = _.compose(exclaim, greet); + * welcome('moe'); + * // => 'hi moe!' + */ + function compose() { + var funcs = arguments; + return function() { + var args = arguments, + length = funcs.length; + + while (length--) { + args = [funcs[length].apply(this, args)]; + } + return args[0]; + }; + } + + /** + * Creates a function that will delay the execution of `func` until after + * `wait` milliseconds have elapsed since the last time it was invoked. Pass + * `true` for `immediate` to cause debounce to invoke `func` on the leading, + * instead of the trailing, edge of the `wait` timeout. Subsequent calls to + * the debounced function will return the result of the last `func` call. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to debounce. + * @param {Number} wait The number of milliseconds to delay. + * @param {Boolean} immediate A flag to indicate execution is on the leading + * edge of the timeout. + * @returns {Function} Returns the new debounced function. + * @example + * + * var lazyLayout = _.debounce(calculateLayout, 300); + * jQuery(window).on('resize', lazyLayout); + */ + function debounce(func, wait, immediate) { + var args, + result, + thisArg, + timeoutId; + + function delayed() { + timeoutId = null; + if (!immediate) { + result = func.apply(thisArg, args); + } + } + return function() { + var isImmediate = immediate && !timeoutId; + args = arguments; + thisArg = this; + + clearTimeout(timeoutId); + timeoutId = setTimeout(delayed, wait); + + if (isImmediate) { + result = func.apply(thisArg, args); + } + return result; + }; + } + + /** + * Executes the `func` function after `wait` milliseconds. Additional arguments + * will be passed to `func` when it is invoked. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to delay. + * @param {Number} wait The number of milliseconds to delay execution. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. + * @returns {Number} Returns the `setTimeout` timeout id. + * @example + * + * var log = _.bind(console.log, console); + * _.delay(log, 1000, 'logged later'); + * // => 'logged later' (Appears after one second.) + */ + function delay(func, wait) { + var args = slice(arguments, 2); + return setTimeout(function() { func.apply(undefined, args); }, wait); + } + + /** + * Defers executing the `func` function until the current call stack has cleared. + * Additional arguments will be passed to `func` when it is invoked. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to defer. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. + * @returns {Number} Returns the `setTimeout` timeout id. + * @example + * + * _.defer(function() { alert('deferred'); }); + * // returns from the function before `alert` is called + */ + function defer(func) { + var args = slice(arguments, 1); + return setTimeout(function() { func.apply(undefined, args); }, 1); + } + + /** + * Creates a function that memoizes the result of `func`. If `resolver` is + * passed, it will be used to determine the cache key for storing the result + * based on the arguments passed to the memoized function. By default, the first + * argument passed to the memoized function is used as the cache key. The `func` + * is executed with the `this` binding of the memoized function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to have its output memoized. + * @param {Function} [resolver] A function used to resolve the cache key. + * @returns {Function} Returns the new memoizing function. + * @example + * + * var fibonacci = _.memoize(function(n) { + * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); + * }); + */ + function memoize(func, resolver) { + var cache = {}; + return function() { + var key = (resolver ? resolver.apply(this, arguments) : arguments[0]) + ''; + return hasOwnProperty.call(cache, key) + ? cache[key] + : (cache[key] = func.apply(this, arguments)); + }; + } + + /** + * Creates a function that is restricted to execute `func` once. Repeat calls to + * the function will return the value of the first call. The `func` is executed + * with the `this` binding of the created function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * var initialize = _.once(createApplication); + * initialize(); + * initialize(); + * // `initialize` executes `createApplication` once + */ + function once(func) { + var ran, + result; + + return function() { + if (ran) { + return result; + } + ran = true; + result = func.apply(this, arguments); + + // clear the `func` variable so the function may be garbage collected + func = null; + return result; + }; + } + + /** + * Creates a function that, when called, invokes `func` with any additional + * `partial` arguments prepended to those passed to the new function. This + * method is similar to `_.bind`, except it does **not** alter the `this` binding. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to partially apply arguments to. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new partially applied function. + * @example + * + * var greet = function(greeting, name) { return greeting + ' ' + name; }; + * var hi = _.partial(greet, 'hi'); + * hi('moe'); + * // => 'hi moe' + */ + function partial(func) { + return createBound(func, slice(arguments, 1)); + } + + /** + * This method is similar to `_.partial`, except that `partial` arguments are + * appended to those passed to the new function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to partially apply arguments to. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new partially applied function. + * @example + * + * var defaultsDeep = _.partialRight(_.merge, _.defaults); + * + * var options = { + * 'variable': 'data', + * 'imports': { 'jq': $ } + * }; + * + * defaultsDeep(options, _.templateSettings); + * + * options.variable + * // => 'data' + * + * options.imports + * // => { '_': _, 'jq': $ } + */ + function partialRight(func) { + return createBound(func, slice(arguments, 1), null, indicatorObject); + } + + /** + * Creates a function that, when executed, will only call the `func` + * function at most once per every `wait` milliseconds. If the throttled + * function is invoked more than once during the `wait` timeout, `func` will + * also be called on the trailing edge of the timeout. Subsequent calls to the + * throttled function will return the result of the last `func` call. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to throttle. + * @param {Number} wait The number of milliseconds to throttle executions to. + * @returns {Function} Returns the new throttled function. + * @example + * + * var throttled = _.throttle(updatePosition, 100); + * jQuery(window).on('scroll', throttled); + */ + function throttle(func, wait) { + var args, + result, + thisArg, + timeoutId, + lastCalled = 0; + + function trailingCall() { + lastCalled = new Date; + timeoutId = null; + result = func.apply(thisArg, args); + } + return function() { + var now = new Date, + remaining = wait - (now - lastCalled); + + args = arguments; + thisArg = this; + + if (remaining <= 0) { + clearTimeout(timeoutId); + timeoutId = null; + lastCalled = now; + result = func.apply(thisArg, args); + } + else if (!timeoutId) { + timeoutId = setTimeout(trailingCall, remaining); + } + return result; + }; + } + + /** + * Creates a function that passes `value` to the `wrapper` function as its + * first argument. Additional arguments passed to the function are appended + * to those passed to the `wrapper` function. The `wrapper` is executed with + * the `this` binding of the created function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Mixed} value The value to wrap. + * @param {Function} wrapper The wrapper function. + * @returns {Function} Returns the new function. + * @example + * + * var hello = function(name) { return 'hello ' + name; }; + * hello = _.wrap(hello, function(func) { + * return 'before, ' + func('moe') + ', after'; + * }); + * hello(); + * // => 'before, hello moe, after' + */ + function wrap(value, wrapper) { + return function() { + var args = [value]; + push.apply(args, arguments); + return wrapper.apply(this, args); + }; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their + * corresponding HTML entities. + * + * @static + * @memberOf _ + * @category Utilities + * @param {String} string The string to escape. + * @returns {String} Returns the escaped string. + * @example + * + * _.escape('Moe, Larry & Curly'); + * // => 'Moe, Larry & Curly' + */ + function escape(string) { + return string == null ? '' : (string + '').replace(reUnescapedHtml, escapeHtmlChar); + } + + /** + * This function returns the first argument passed to it. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Mixed} value Any value. + * @returns {Mixed} Returns `value`. + * @example + * + * var moe = { 'name': 'moe' }; + * moe === _.identity(moe); + * // => true + */ + function identity(value) { + return value; + } + + /** + * Adds functions properties of `object` to the `lodash` function and chainable + * wrapper. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Object} object The object of function properties to add to `lodash`. + * @example + * + * _.mixin({ + * 'capitalize': function(string) { + * return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); + * } + * }); + * + * _.capitalize('moe'); + * // => 'Moe' + * + * _('moe').capitalize(); + * // => 'Moe' + */ + function mixin(object) { + forEach(functions(object), function(methodName) { + var func = lodash[methodName] = object[methodName]; + + lodash.prototype[methodName] = function() { + var args = [this.__wrapped__]; + push.apply(args, arguments); + return new lodash(func.apply(lodash, args)); + }; + }); + } + + /** + * Reverts the '_' variable to its previous value and returns a reference to + * the `lodash` function. + * + * @static + * @memberOf _ + * @category Utilities + * @returns {Function} Returns the `lodash` function. + * @example + * + * var lodash = _.noConflict(); + */ + function noConflict() { + window._ = oldDash; + return this; + } + + /** + * Produces a random number between `min` and `max` (inclusive). If only one + * argument is passed, a number between `0` and the given number will be returned. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Number} [min=0] The minimum possible value. + * @param {Number} [max=1] The maximum possible value. + * @returns {Number} Returns a random number. + * @example + * + * _.random(0, 5); + * // => a number between 0 and 5 + * + * _.random(5); + * // => also a number between 0 and 5 + */ + function random(min, max) { + if (min == null && max == null) { + max = 1; + } + min = +min || 0; + if (max == null) { + max = min; + min = 0; + } + return min + floor(nativeRandom() * ((+max || 0) - min + 1)); + } + + /** + * Resolves the value of `property` on `object`. If `property` is a function, + * it will be invoked and its result returned, else the property value is + * returned. If `object` is falsey, then `null` is returned. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Object} object The object to inspect. + * @param {String} property The property to get the value of. + * @returns {Mixed} Returns the resolved value. + * @example + * + * var object = { + * 'cheese': 'crumpets', + * 'stuff': function() { + * return 'nonsense'; + * } + * }; + * + * _.result(object, 'cheese'); + * // => 'crumpets' + * + * _.result(object, 'stuff'); + * // => 'nonsense' + */ + function result(object, property) { + var value = object ? object[property] : undefined; + return isFunction(value) ? object[property]() : value; + } + + /** + * A micro-templating method that handles arbitrary delimiters, preserves + * whitespace, and correctly escapes quotes within interpolated code. + * + * Note: In the development build, `_.template` utilizes sourceURLs for easier + * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl + * + * Note: Lo-Dash may be used in Chrome extensions by either creating a `lodash csp` + * build and using precompiled templates, or loading Lo-Dash in a sandbox. + * + * For more information on precompiling templates see: + * http://lodash.com/#custom-builds + * + * For more information on Chrome extension sandboxes see: + * http://developer.chrome.com/stable/extensions/sandboxingEval.html + * + * @static + * @memberOf _ + * @category Utilities + * @param {String} text The template text. + * @param {Obect} data The data object used to populate the text. + * @param {Object} options The options object. + * escape - The "escape" delimiter regexp. + * evaluate - The "evaluate" delimiter regexp. + * interpolate - The "interpolate" delimiter regexp. + * sourceURL - The sourceURL of the template's compiled source. + * variable - The data object variable name. + * + * @returns {Function|String} Returns a compiled function when no `data` object + * is given, else it returns the interpolated text. + * @example + * + * // using a compiled template + * var compiled = _.template('hello <%= name %>'); + * compiled({ 'name': 'moe' }); + * // => 'hello moe' + * + * var list = '<% _.forEach(people, function(name) { %>
  • <%= name %>
  • <% }); %>'; + * _.template(list, { 'people': ['moe', 'larry'] }); + * // => '
  • moe
  • larry
  • ' + * + * // using the "escape" delimiter to escape HTML in data property values + * _.template('<%- value %>', { 'value': ' - + - + - +