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
[](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'] });
+ * // => 'moelarry'
+ *
+ * // using the "escape" delimiter to escape HTML in data property values
+ * _.template('<%- value %>', { 'value': '
-
+
-
+
-
+